1# == Makefile for Trac related tasks
2#
3# Automating testing, l10n tasks, documentation generation, ... see HELP below
4# ----------------------------------------------------------------------------
5#
6# Note about customization:
7#
8#   No changes to the present Makefile should be necessary,
9#   configuration should take place in the Makefile.cfg file.
10#
11#   Copy Makefile.cfg.sample to Makefile.cfg and adapt it
12#   to match your local environment.
13#
14# Note that this is a GNU Makefile, nmake and other abominations are
15# not supported. On Windows, you can use it from a msys2 shell, like
16# the one that comes part of https://git-for-windows.github.io.
17#
18# ============================================================================
19
20define HELP
21
22 The Trac Makefile is here to help automate development and
23 maintenance tasks.
24
25 Please use `make <target>' where <target> is one of:
26
27  clean               delete all compiled files
28  status              show which Python is used and other infos
29
30  [python=...]        variable for selecting Python version
31  [pythonopts=...]    variable containing extra options for the interpreter
32
33 As there are many more tasks available, you can ask for specific help:
34
35  help-code           tasks for checking the code style
36  help-testing        tasks and configuration parameters for testing
37  help-server         tasks and configuration for starting tracd
38  help-l10n           tasks and configuration for L10N maintenance
39  help-doc            tasks and configuration for preparing Trac documentation
40  help-release        tasks and configuration for preparing a Trac release
41  help-misc           several other tasks
42
43  help-all            all the tasks at a glance...
44endef
45# `
46export HELP
47
48define HELP_CFG
49 It looks like you don't have a Makefile.cfg file yet.
50 You can get started by doing `cp Makefile.cfg.sample Makefile.cfg'
51 and then adapt it to your environment.
52endef
53export HELP_CFG
54
55# ============================================================================
56
57# ----------------------------------------------------------------------------
58#
59# Main targets
60#
61# ----------------------------------------------------------------------------
62
63.PHONY: all help help-all status clean clean-bytecode clean-mo
64
65%.py : status
66	$(PYTHON) -m unittest $(testopts) $(subst /,.,$(@:.py=)).test_suite
67
68ifdef test
69all: status
70	$(PYTHON) -m unittest $(testopts) $(subst /,.,$(test:.py=)).test_suite
71else
72all: help
73endif
74
75help: Makefile.cfg
76	@echo "$$HELP"
77
78help_variables = $(filter HELP_%,$(.VARIABLES))
79help_targets = $(filter-out help-CFG,$(help_variables:HELP_%=help-%))
80
81.SECONDEXPANSION:
82help-all: $$(sort $$(help_targets))
83
84
85help-%: Makefile.cfg
86	@echo "$${HELP_$*}"
87
88Makefile.cfg:
89	@echo "$$HELP_CFG"
90
91status:
92	@echo
93	@echo "Python: $$(which $(PYTHON)) $(pythonopts)"
94	@echo
95	@$(PYTHON) contrib/make_status.py
96	@echo
97	@echo "Variables:"
98	@echo "  PATH=$$PATH"
99	@echo "  PYTHONPATH=$$PYTHONPATH"
100	@echo "  TRAC_TEST_DB_URI=$$TRAC_TEST_DB_URI"
101	@echo "  server-options=$(server-options)"
102	@echo
103	@echo "External dependencies:"
104	@printf "  Git version: "
105	@git --version 2>/dev/null || echo "not installed"
106	@printf "  Subversion version: "
107	@svn --version -q 2>/dev/null || echo "not installed"
108	@echo
109
110Trac.egg-info: status
111	$(PYTHON) setup.py egg_info
112
113clean: clean-bytecode clean-coverage clean-doc
114
115clean-bytecode:
116	find . -name \*.py[co] -exec rm {} \;
117
118Makefile: ;
119
120# ----------------------------------------------------------------------------
121#
122# Copy Makefile.cfg.sample to Makefile.cfg and adapt to your local
123# environment, no customization of the present Makefile should be
124# necessary.
125#
126#
127-include Makefile.cfg
128#
129# ----------------------------------------------------------------------------
130
131
132# ----------------------------------------------------------------------------
133#
134# L10N related tasks
135#
136# ----------------------------------------------------------------------------
137
138define HELP_l10n
139
140 ---------------- L10N tasks
141
142  init-xy             create catalogs for given xy locale
143
144  extraction          regenerate the catalog templates
145
146  update              update all the catalog files from the templates
147  update-xy           update the catalogs for the xy locale only
148  [updateopts=...]    variable containing extra options for update (e.g. -N)
149
150  compile             compile all the catalog files
151  compile-xy          compile the catalogs for the xy locale only
152
153  check               verify all the catalog files
154  check-xy            verify the catalogs for the xy locale only
155
156  stats               detailed translation statistics for all catalogs
157  stats-pot           total messages in the catalog templates
158  stats-xy            translated, fuzzy, untranslated for the xy locale only
159
160  summary             display percent translated for all catalogs
161  summary-xy          display percent translated for the xy locale only
162                      (suitable for a commit message)
163
164  tx-merge            merge catalogs from Transifex for all locales
165  tx-merge-xy         merge catalogs from Transifex for the xy locale only
166
167  xdiff               show changes to message strings for all catalogs
168  xdiff-pot           show changes to message strings in the catalog template
169  xdiff-xy            show changes to message strings for the xy locale
170
171  diff                show summarized changes after an update for all catalogs
172  diff-xy             show summarized changes after an update for the xy locale
173  [vc=...]            variable containing the version control command to use
174
175  [locale=...]        variable for selecting a set of locales
176
177endef
178export HELP_l10n
179
180catalogs = messages messages-js tracini
181
182ifdef locale
183    locales = $(locale)
184else
185    locales = $(wildcard trac/locale/*/LC_MESSAGES/messages.po)
186    locales := $(subst trac/locale/,,$(locales))
187    locales := $(subst /LC_MESSAGES/messages.po,,$(locales))
188    locales := $(sort $(locales))
189endif
190
191# Note: variables only valid within a $(foreach catalog,...) evaluation
192catalog.po = trac/locale/$(*)/LC_MESSAGES/$(catalog).po
193catalog.pot = trac/locale/$(catalog).pot
194catalog_stripped = $(subst messages,,$(subst -,,$(catalog)))
195_catalog = $(if $(catalog_stripped),_)$(catalog_stripped)
196
197.PHONY: extract extraction update compile check stats summary diff
198
199
200init-%:
201	@$(foreach catalog,$(catalogs), \
202	    [ -e $(catalog.po) ] \
203	    && echo "$(catalog.po) already exists" \
204	    || $(PYTHON) setup.py init_catalog$(_catalog) -l $(*);)
205
206
207extract extraction:
208	$(PYTHON) setup.py $(foreach catalog,$(catalogs),\
209	    extract_messages$(_catalog))
210
211
212update-%:
213	$(PYTHON) setup.py $(foreach catalog,$(catalogs), \
214	    update_catalog$(_catalog) -l $(*)) $(updateopts)
215
216ifdef locale
217update: $(addprefix update-,$(locale))
218else
219update:
220	$(PYTHON) setup.py $(foreach catalog,$(catalogs), \
221	    update_catalog$(_catalog)) $(updateopts)
222endif
223
224
225compile-%:
226	$(PYTHON) setup.py $(foreach catalog,$(catalogs), \
227	    compile_catalog$(_catalog) -l $(*)) \
228	    generate_messages_js -l $(*)
229
230ifdef locale
231compile: $(addprefix compile-,$(locale))
232else
233compile:
234	$(PYTHON) setup.py $(foreach catalog,$(catalogs), \
235	    compile_catalog$(_catalog)) generate_messages_js
236endif
237
238
239check: pre-check $(addprefix check-,$(locales))
240	@echo "All catalogs checked are OK"
241
242pre-check:
243	@echo "checking catalogs for $(locales)..."
244
245check-%:
246	@printf "$(@): "
247	$(PYTHON) setup.py $(foreach catalog,$(catalogs), \
248	    check_catalog$(_catalog) -l $(*))
249	@$(foreach catalog,$(catalogs), \
250	    msgfmt --check $(catalog.po) &&) echo msgfmt OK
251	@rm -f messages.mo
252
253
254stats: pre-stats $(addprefix stats-,$(locales))
255
256pre-stats: stats-pot
257	@echo "translation statistics for $(locales)..."
258
259stats-pot:
260	@echo "translation statistics for catalog templates:"
261	@$(foreach catalog,$(catalogs), \
262	    printf "$(catalog.pot): "; \
263	    msgfmt --statistics $(catalog.pot);)
264	@rm -f messages.mo
265
266stats-%:
267	@$(foreach catalog,$(catalogs), \
268	    [ -e $(catalog.po) ] \
269	    && { printf "$(catalog.po): "; \
270	         msgfmt --statistics $(catalog.po); } \
271	    || echo "$(catalog.po) doesn't exist (make init-$(*))";)
272	@rm -f messages.mo
273
274
275summary: $(addprefix summary-,$(locales))
276
277define untranslated-sh
278LC_ALL=C msgfmt --statistics $(catalog.pot) 2>&1 \
279  | tail -1 \
280  | sed -e 's/0 translated messages, \([0-9]*\) un.*/\1/'
281endef
282
283define translated-sh
284{ LC_ALL=C msgfmt --statistics $(catalog.po) 2>&1 || echo 0; } \
285    | tail -1 \
286    | sed -e 's/[^0-9]*\([0-9]*\) translated.*/\1/'
287endef
288
289MESSAGES_TOTAL = \
290    $(eval MESSAGES_TOTAL := ($(foreach catalog,$(catalogs), \
291                                  $(shell $(untranslated-sh)) + ) 0)) \
292    $(MESSAGES_TOTAL)
293
294summary-%:
295	@$(PYTHON) -c "print('l10n/$(*): translations updated (%d%%)' \
296	    % (($(foreach catalog,$(catalogs), \
297	          $(shell $(translated-sh)) + ) 0) * 100.0 \
298	       / $(MESSAGES_TOTAL)))"
299	@rm -f messages.mo
300
301
302tx-merge-%:
303	@$(foreach catalog,$(catalogs), \
304	    touch $(catalog.po); \
305	    mv $(catalog.po) $(catalog.po).orig; \
306	)
307	@tx pull --force --no-interactive -l $(*)
308	@$(foreach catalog,$(catalogs), \
309	    [ -f $(catalog.po) ] && mv $(catalog.po) $(catalog.po).tx; \
310	    mv $(catalog.po).orig $(catalog.po); \
311	    [ -f $(catalog.po).tx ] && { \
312	        $(PYTHON) contrib/merge_catalog.py $(catalog) $(catalog.po).tx $(*); \
313	        rm $(catalog.po).tx; \
314	    }; \
315	)
316
317tx-merge: $(addprefix tx-merge-,$(filter-out en_US,$(locales)))
318
319
320diff: $(addprefix diff-,$(locales))
321
322ifndef vc
323ifneq "$(wildcard $(CURDIR)/.svn)" ""
324    vc := svn
325else ifneq "$(wildcard $(CURDIR)/.git)" ""
326    vc := git
327else ifneq "$(wildcard $(CURDIR)/.hg)" ""
328    vc := hg
329endif
330endif
331
332define vc_cat-svn
333    $(vc) cat -rBASE $(1)
334endef
335define vc_cat-git
336    $(vc) show HEAD:$(1)
337endef
338define vc_cat-hg
339    $(vc) cat -r. $(1)
340endef
341define vc_cat
342    $(call vc_cat-$(vc),$(1))
343endef
344
345define xdiff_catalog
346    { \
347        $(call vc_cat,$(1)) \
348            | msgcat --no-wrap --no-location -s -i - >$(1).tmp; \
349        msgcat --no-wrap --no-location -s -i - <$(1) \
350            | diff -u -L $(1) -L $(1) $(1).tmp - || :; \
351        rm -- $(1).tmp; \
352    }
353endef
354
355
356xdiff-pot:
357	@$(foreach catalog,$(catalogs), \
358	    $(call xdiff_catalog,$(catalog.pot)); \
359	)
360
361xdiff-%:
362	@$(foreach catalog,$(catalogs), \
363	    $(call xdiff_catalog,$(catalog.po)); \
364	)
365
366xdiff: $(addprefix xdiff-,$(locales))
367
368diff-%:
369	@diff=l10n-$(*).diff; \
370	$(vc) diff trac/locale/$(*) > $$diff; \
371	[ -s $$diff ] && { \
372	    printf "# $(*) changed -> "; \
373	    $(PYTHON) contrib/l10n_diff_index.py $$diff; \
374	} || rm $$diff
375
376# The above creates l10n-xy.diff files but also a l10n-xy.diff.index
377# file pointing to "interesting" diffs (message changes or addition
378# for valid msgid).
379#
380# See also contrib/l10n_sanitize_diffs.py, which removes in-file
381# *conflicts* for line change only.
382
383clean-mo:
384	find trac/locale -name \*.mo -exec rm {} \;
385	find trac/htdocs/js/messages -name \*.js -exec rm {} \;
386
387
388# ----------------------------------------------------------------------------
389#
390# Code checking tasks
391#
392# ----------------------------------------------------------------------------
393
394define HELP_code
395
396 ---------------- Code checking tasks
397
398  pylint              check code with pylint
399  jinja               check Jinja2 templates
400  coffee              compile .coffee script files into .js files
401
402  [module=...]        module or package to check with pylint
403  [templates=...]     list of Jinja2 templates to check
404  [coffeescripts=...] list of coffee script files to compile
405  [jinjaopts=]        list of options for jinja checker
406
407endef
408export HELP_code
409
410.PHONY: pylint jinja coffee
411
412pylintopts = --persistent=n --init-import=y \
413--disable=E0102,E0211,E0213,E0602,E0611,E1002,E1101,E1102,E1103 \
414--disable=F0401 \
415--disable=W0102,W0141,W0142,W0201,W0212,W0221,W0223,W0231,W0232, \
416--disable=W0401,W0511,W0603,W0613,W0614,W0621,W0622,W0703 \
417--disable=C0103,C0111 \
418
419ifdef module
420pylint:
421	pylint $(pylintopts) $(subst /,.,$(module:.py=))
422else
423pylint:
424	pylint $(pylintopts) trac tracopt
425endif
426
427
428templates ?= $(shell \
429    find $$(find trac tracopt -type d -a -name templates) \
430        -mindepth 1 -maxdepth 1 -type f | \
431    grep -v "~" | grep -v README )
432
433jinja:
434	$(PYTHON) contrib/jinjachecker.py $(jinjaopts) $(templates)
435
436coffeescripts ?= $(shell find trac tracopt -name \*.coffee)
437coffeescript_major_version ?= \
438    $(shell coffee -v 2> /dev/null | \
439        sed -E s'/CoffeeScript version ([0-9]+)\.[0-9]+\.[0-9]+/\1/')
440
441coffee:
442ifneq "$(coffeescript_major_version)" "1"
443	$(error "CoffeeScript version 1.x is required")
444endif
445	coffee -c $(coffeescripts)
446
447
448# ----------------------------------------------------------------------------
449#
450# Testing related tasks
451#
452# ----------------------------------------------------------------------------
453
454define HELP_testing
455
456 ---------------- Testing tasks
457
458  unit-test           run unit tests
459  functional-test     run functional tests
460  test-wiki           shortcut for running all wiki unit tests
461  test                run all tests
462  coverage            run all tests, under coverage
463
464  [db=...]            variable for selecting database backend
465  [test=...]          variable for selecting a single test file
466  [testopts=...]      variable containing extra options for running tests
467  [coverageopts=...]  variable containing extra options for coverage
468
469endef
470export HELP_testing
471
472.PHONY: test unit-test functional-test test-wiki
473
474test: unit-test functional-test
475
476unit-test: Trac.egg-info
477	SKIP_FUNCTIONAL_TESTS=1 $(PYTHON) -m unittest $(testopts) trac.test.test_suite
478
479functional-test: Trac.egg-info
480	$(PYTHON) -m unittest $(testopts) trac.tests.functional.test_suite
481
482test-wiki:
483	$(PYTHON) -m unittest $(testopts) trac.tests.allwiki.test_suite
484
485# ----------------------------------------------------------------------------
486#
487# Coverage related tasks
488#
489# (see http://nedbatchelder.com/code/coverage/)
490#
491# ----------------------------------------------------------------------------
492
493COVERAGEOPTS ?= --branch --source=trac,tracopt
494
495.PHONY: coverage clean-coverage show-coverage
496
497coverage: clean-coverage test-coverage show-coverage
498
499clean-coverage:
500	coverage erase
501	@rm -fr htmlcov
502
503ifdef test
504test-coverage:
505	coverage run $(test) $(testopts)
506else
507test-coverage: unit-test-coverage
508endif
509
510unit-test-coverage:
511	coverage run -a $(coverageopts) $(COVERAGEOPTS) \
512	    trac/test.py --skip-functional-tests $(testopts)
513
514show-coverage: htmlcov/index.html
515	$(if $(START),$(START) $(<))
516
517htmlcov/index.html:
518	coverage html --omit=*/__init__.py
519
520
521# ----------------------------------------------------------------------------
522#
523# Tracd related tasks
524#
525# ----------------------------------------------------------------------------
526
527define HELP_server
528
529 ---------------- Standalone test server
530
531  [start-]server      start tracd
532
533  [port=...]          variable for selecting the port
534  [auth=...]          variable for specifying authentication
535  [env=...]           variable for the trac environment or parent dir
536  [tracdopts=...]     variable containing extra options for tracd
537
538endef
539export HELP_server
540
541port ?= 8000
542tracdopts ?= -r
543
544define server-options
545 $(if $(port),-p $(port))\
546 $(if $(auth),-a '$(auth)')\
547 $(tracdopts)\
548 $(if $(wildcard $(env)/VERSION),$(env),-e $(env))
549endef
550
551.PHONY: server start-server tracd start-tracd
552
553server tracd start-tracd: start-server
554
555start-server: Trac.egg-info
556ifdef env
557	$(PYTHON) trac/web/standalone.py $(server-options)
558else
559	@echo "\`env' variable was not specified. See \`make help'."
560endif
561
562
563
564# ----------------------------------------------------------------------------
565#
566# Miscellaneous tasks
567#
568# ----------------------------------------------------------------------------
569
570define HELP_misc
571 ---------------- Miscellaneous
572
573  start-admin         start trac-admin (on `env')
574  start-python        start the Python interpreter
575
576  [adminopts=...]     variable containing extra options for trac-admin
577
578endef
579# ` (keep emacs font-lock happy)
580export HELP_misc
581
582
583.PHONY: trac-admin start-admin
584
585trac-admin: start-admin
586
587start-admin:
588ifneq "$(wildcard $(env)/VERSION)" ""
589	@$(PYTHON) trac/admin/console.py $(env) $(adminopts)
590else
591	@echo "\`env' variable was not specified or doesn't point to one env."
592endif
593
594
595.PHONY: start-python
596
597start-python:
598	@$(PYTHON)
599# (this doesn't seem to be much, but we're taking benefit of the
600# environment setup we're doing below)
601
602
603# ----------------------------------------------------------------------------
604#
605# Documentation related tasks
606#
607# ----------------------------------------------------------------------------
608
609define HELP_doc
610
611 ---------------- Documentation tasks
612
613  apidoc|sphinx       generate the Sphinx documentation (all specified formats)
614  apidoc-html         generate the Sphinx documentation in HTML format
615  apidoc-pdf          generate the Sphinx documentation in PDF format
616  apidoc-check        check for missing symbols in Sphinx documentation
617  apidoc-coverage     generate coverage information for Sphinx documentation
618
619  apiref|epydoc       generate the full API reference using Epydoc
620
621  [sphinxformat=...]  list of formats for generated documentation
622  [sphinxopts=...]    variable containing extra options for Sphinx
623  [sphinxopts-html=...] variable containing extra options used for html format
624  [epydocopts=...]    variable containing extra options for Epydoc
625  [dotpath=/.../dot]  path to Graphviz dot program (not used yet)
626endef
627export HELP_doc
628
629.PHONY: apidoc sphinx apidoc-check apiref epydoc clean-doc
630
631# We also try to honor the "conventional" environment variables used by Sphinx
632sphinxopts ?= $(SPHINXOPTS)
633SPHINXBUILD ?= sphinx-build
634BUILDDIR ?= build/doc
635PAPER ?= a4
636sphinxopts-latex ?= -D latex_paper_size=$(PAPER)
637sphinxformat = html
638
639sphinx: apidoc
640apidoc: $(addprefix apidoc-,$(sphinxformat))
641
642apidoc-check:
643	@$(PYTHON) doc/utils/checkapidoc.py
644
645apidoc-%:
646	@$(SPHINXBUILD) -b $(*) \
647	    $(sphinxopts) $(sphinxopts-$(*)) \
648	    -d build/doc/doctree \
649	    doc $(BUILDDIR)/$(*)
650	@$(if $(findstring coverage,$(*)),\
651	    diff -u doc/utils/python.txt $(BUILDDIR)/coverage/python.txt)
652
653
654epydoc: apiref
655apiref: doc-images
656	@$(PYTHON) doc/utils/runepydoc.py --config=doc/utils/epydoc.conf \
657	    $(epydocopts) $(if $(dotpath),--dotpath=$(dotpath))
658
659doc-images: $(addprefix build/,$(wildcard doc/images/*.png))
660build/doc/images/%: doc/images/% | build/doc/images
661	@cp $(<) $(@)
662build/doc/images:
663	@mkdir -p $(@)
664
665clean-doc:
666	rm -fr build/doc
667
668
669# ----------------------------------------------------------------------------
670#
671# Release related tasks
672#
673# ----------------------------------------------------------------------------
674
675define HELP_release
676
677 ---------------- Release tasks
678
679  release             release-exe on Windows, release-src otherwise
680  release-src         generate the .tar.gz and .whl packages
681  release-exe         generate the Windows installers (32- and 64-bits)
682  release-clean       remove the packages
683
684  update-help         fetches latest help/guide from Edgewall
685  update-copyright    update copyright year in file headers
686  checksum            MD5 and SHA1 checksums of packages of given version
687  upload              scp the packages of given version to user@lynx:~/dist
688
689  [version=...]       version number, mandatory for checksum and upload
690  [prefix=...]        wiki page prefix for update-help
691endef
692export HELP_release
693
694.PHONY: release release-src wheel dist release-exe wininst
695.PHONY: release-clean checksum update-copyright update-help upload
696
697ifeq "$(OS)" "Windows_NT"
698release: release-exe
699else # !Windows_NT
700release: release-src
701endif # Windows_NT
702
703release-clean:
704ifeq "$(version)" ""
705	$(error "specify version= on the make command-line")
706else
707	@rm $(sdist+wheel) $(wininst)
708endif
709
710user ?= $(or $(USER),$(LOGNAME),$(USERNAME))
711lynx = $(user)@lynx.edgewall.com:/home/$(user)/dist
712SCP ?= scp
713
714release-src: wheel sdist
715
716wheel:
717	@$(PYTHON) setup.py bdist_wheel
718sdist:
719	@$(PYTHON) setup.py sdist
720
721sdist+wheel = $(sdist_gztar) $(bdist_wheel)
722
723sdist_gztar = dist/Trac-$(version).tar.gz
724bdist_wheel = dist/Trac-$(version)-py2-none-any.whl
725
726
727ifeq "$(OS)" "Windows_NT"
728release-exe:
729ifdef python.x86
730	make python=x86 wininst
731else
732	$(error "define python.x86 in Makefile.cfg for building $(wininst.x86)")
733endif
734ifdef python.x64
735	make python=x64 wininst
736else
737	$(error "define python.x64 in Makefile.cfg for building $(wininst.x64)")
738endif
739
740wininst = $(wininst.x86) $(wininst.x64)
741
742wininst.x86 = dist/Trac-$(version).win32.exe
743wininst.x64 = dist/Trac-$(version).win-amd64.exe
744
745wininst:
746	@$(PYTHON) setup.py bdist_wininst
747endif # Windows_NT
748
749packages = $(wildcard $(sdist+wheel) $(wininst))
750
751checksum:
752ifeq "$(version)" ""
753	$(error "specify version= on the make command-line")
754else
755	@echo "Packages for Trac-$(version):"
756	@echo
757	@$(if $(packages), \
758	    $(PYTHON) contrib/checksum.py md5:sha256 $(packages) \
759	, \
760	    echo "No packages found: $(sdist+wheel) $(wininst)" \
761	)
762endif
763
764copyright_re := "s/^($$PREFIX)(Copyright \(C\) 20[0-9][0-9])(-20[0-9][0-9])?$\
765    ( Edgewall Software\r?)$$/\1\2-$(year)\4/g"
766
767update-copyright:
768ifeq "$(year)" ""
769	$(error "specify year= on the make command-line")
770else
771	$(eval SED = $(if \
772	    $(shell sed --version 2>/dev/null | grep -qF "(GNU sed)" \
773	      && echo 1), \
774	    sed --in-place= -r -e, \
775	    sed -i '' -E))
776	@PREFIX="\{\#\s+"; find . -type f -name "*.html" \
777	    -exec $(SED) $(copyright_re) {} \;
778	@PREFIX="\# "; find . -type f \
779	    \( -name "*.py" -o -name "*.po" -o -name "*.pot" -o \
780	       -name "*.ps1" -o -name "*.cgi" -o -name "*.fcgi" \) \
781	    -exec $(SED) $(copyright_re) {} \;
782	@PREFIX="\# "; $(SED) $(copyright_re) \
783	    contrib/trac-svn-hook contrib/trac-pre-commit-hook
784	@PREFIX=":: "; $(SED) $(copyright_re) \
785	    contrib/trac-svn-post-commit-hook.cmd
786	@PREFIX=; $(SED) $(copyright_re) COPYING
787	@$(SED) "s/( year *= *)'(20[0-9][0-9])-20[0-9][0-9]'/\1'\2-$(year)'/" \
788	    trac/admin/console.py trac/templates/about.html
789endif
790
791update-help:
792	@$(PYTHON) contrib/checkwiki.py -d --prefix=$(prefix)
793	@$(PYTHON) contrib/wiki2rst.py TracUpgrade > UPGRADE.rst
794	@$(PYTHON) contrib/wiki2rst.py TracInstall > INSTALL.rst
795
796upload: checksum
797ifeq "$(user)" ""
798	$(error "define user in Makefile.cfg for uploading to lynx")
799else
800	$(if $(packages),$(SCP) $(packages) $(lynx))
801endif # user
802
803
804
805# ============================================================================
806#
807# Setup environment variables
808
809PYTHON ?= python
810PYTHON := $(PYTHON) $(pythonopts)
811
812python-home := $(python.$(or $(python),$($(db).python)))
813
814ifeq "$(findstring ;,$(PATH))" ";"
815    SEP = ;
816    START ?= start
817else
818    SEP = :
819    START ?= xdg-open
820endif
821
822ifeq "$(OS)" "Windows_NT"
823    ifndef python-home
824        # Detect location of current python
825        python-exe := $(shell python -c 'import sys; print(sys.executable)')
826        python-home := $(subst \python.exe,,$(python-exe))
827        ifeq "$(SEP)" ":"
828            python-home := /$(subst :,,$(subst \,/,$(python-home)))
829        endif
830    endif
831    python-bin = $(python-home)$(SEP)$(python-home)/Scripts
832endif
833
834define prepend-path
835$(if $2,$(if $1,$1$(SEP)$2,$2),$1)
836endef
837
838PATH-extension = $(call prepend-path,$(python-bin),$(path.$(python)))
839PYTHONPATH-extension = $(call prepend-path,.,$(pythonpath.$(python)))
840
841export PATH := $(call prepend-path,$(PATH-extension),$(PATH))
842export PYTHONPATH := $(call prepend-path,$(PYTHONPATH-extension),$(PYTHONPATH))
843export TRAC_TEST_DB_URI = $($(db).uri)
844
845# Misc.
846space = $(empty) $(empty)
847comma = ,
848# ----------------------------------------------------------------------------
849