aboutsummaryrefslogtreecommitdiffstats
path: root/lib/reltool
diff options
context:
space:
mode:
Diffstat (limited to 'lib/reltool')
-rw-r--r--lib/reltool/AUTHORS4
-rw-r--r--lib/reltool/Makefile37
-rw-r--r--lib/reltool/doc/html/.gitignore0
-rw-r--r--lib/reltool/doc/man3/.gitignore0
-rw-r--r--lib/reltool/doc/pdf/.gitignore0
-rw-r--r--lib/reltool/doc/src/Makefile105
-rw-r--r--lib/reltool/doc/src/book.gifbin0 -> 1081 bytes
-rw-r--r--lib/reltool/doc/src/book.xml48
-rw-r--r--lib/reltool/doc/src/fascicules.xml17
-rw-r--r--lib/reltool/doc/src/files.mk45
-rw-r--r--lib/reltool/doc/src/make.dep20
-rw-r--r--lib/reltool/doc/src/note.gifbin0 -> 1539 bytes
-rw-r--r--lib/reltool/doc/src/notes.gifbin0 -> 2005 bytes
-rw-r--r--lib/reltool/doc/src/notes.xml186
-rw-r--r--lib/reltool/doc/src/part.xml43
-rw-r--r--lib/reltool/doc/src/part_notes.xml41
-rw-r--r--lib/reltool/doc/src/ref_man.gifbin0 -> 1530 bytes
-rw-r--r--lib/reltool/doc/src/ref_man.xml41
-rw-r--r--lib/reltool/doc/src/reltool.xml698
-rw-r--r--lib/reltool/doc/src/reltool_examples.xml466
-rw-r--r--lib/reltool/doc/src/reltool_intro.xml98
-rw-r--r--lib/reltool/doc/src/reltool_usage.xml393
-rw-r--r--lib/reltool/doc/src/user_guide.gifbin0 -> 1581 bytes
-rw-r--r--lib/reltool/doc/src/warning.gifbin0 -> 1498 bytes
-rw-r--r--lib/reltool/ebin/.gitignore0
-rw-r--r--lib/reltool/examples/Makefile79
-rw-r--r--lib/reltool/examples/display_args8
-rwxr-xr-xlib/reltool/examples/mnesia_core_dump_viewer54
-rw-r--r--lib/reltool/info7
-rw-r--r--lib/reltool/src/Makefile99
-rw-r--r--lib/reltool/src/reltool.app.src37
-rw-r--r--lib/reltool/src/reltool.erl253
-rw-r--r--lib/reltool/src/reltool.hrl202
-rw-r--r--lib/reltool/src/reltool_app_win.erl886
-rw-r--r--lib/reltool/src/reltool_fgraph.erl163
-rw-r--r--lib/reltool/src/reltool_fgraph.hrl44
-rw-r--r--lib/reltool/src/reltool_fgraph_win.erl726
-rw-r--r--lib/reltool/src/reltool_mod_win.erl773
-rw-r--r--lib/reltool/src/reltool_server.erl1678
-rw-r--r--lib/reltool/src/reltool_sys_win.erl1292
-rw-r--r--lib/reltool/src/reltool_target.erl1226
-rw-r--r--lib/reltool/src/reltool_utils.erl555
-rw-r--r--lib/reltool/vsn.mk26
43 files changed, 10350 insertions, 0 deletions
diff --git a/lib/reltool/AUTHORS b/lib/reltool/AUTHORS
new file mode 100644
index 0000000000..ccc3a9724b
--- /dev/null
+++ b/lib/reltool/AUTHORS
@@ -0,0 +1,4 @@
+Original author:
+Hakan Mattsson
+
+Contributors:
diff --git a/lib/reltool/Makefile b/lib/reltool/Makefile
new file mode 100644
index 0000000000..c3f6d855b4
--- /dev/null
+++ b/lib/reltool/Makefile
@@ -0,0 +1,37 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Macros
+# ----------------------------------------------------
+
+SUB_DIRECTORIES = src examples doc/src
+
+include vsn.mk
+VSN = $(RELTOOL_VSN)
+
+SPECIAL_TARGETS =
+
+# ----------------------------------------------------
+# Default Subdir Targets
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_subdir.mk
+
diff --git a/lib/reltool/doc/html/.gitignore b/lib/reltool/doc/html/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/reltool/doc/html/.gitignore
diff --git a/lib/reltool/doc/man3/.gitignore b/lib/reltool/doc/man3/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/reltool/doc/man3/.gitignore
diff --git a/lib/reltool/doc/pdf/.gitignore b/lib/reltool/doc/pdf/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/reltool/doc/pdf/.gitignore
diff --git a/lib/reltool/doc/src/Makefile b/lib/reltool/doc/src/Makefile
new file mode 100644
index 0000000000..2c634bdf6c
--- /dev/null
+++ b/lib/reltool/doc/src/Makefile
@@ -0,0 +1,105 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../../vsn.mk
+VSN=$(RELTOOL_VSN)
+APPLICATION=reltool
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/$(APPLICATION)-$(VSN)
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+include files.mk
+
+# ----------------------------------------------------
+
+HTML_FILES = $(XML_APPLICATION_FILES:%.xml=$(HTMLDIR)/%.html) \
+ $(XML_PART_FILES:%.xml=$(HTMLDIR)/%.html)
+
+INFO_FILE = ../../info
+
+MAN3_FILES = $(XML_REF3_FILES:%.xml=$(MAN3DIR)/%.3)
+
+XML_FILES = \
+ $(BOOK_FILES) $(XML_CHAPTER_FILES) \
+ $(XML_PART_FILES) $(XML_REF3_FILES) $(XML_APPLICATION_FILES)
+
+HTML_REF_MAN_FILE = $(HTMLDIR)/index.html
+
+TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf
+
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+XML_FLAGS +=
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+$(HTMLDIR)/%.gif: %.gif
+ $(INSTALL_DATA) $< $@
+
+docs: pdf html man
+
+$(TOP_PDF_FILE): $(XML_FILES)
+
+pdf: $(TOP_PDF_FILE)
+
+html: gifs $(HTML_REF_MAN_FILE)
+
+man: $(MAN3_FILES)
+
+gifs: $(GIF_FILES:%=$(HTMLDIR)/%)
+
+debug opt:
+
+clean clean_docs:
+ rm -rf $(HTMLDIR)/*
+ rm -f $(MAN3DIR)/*
+ rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo)
+ rm -f errs core *~
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_docs_spec: docs
+ $(INSTALL_DIR) $(RELSYSDIR)/doc/pdf
+ $(INSTALL_DATA) $(TOP_PDF_FILE) $(RELSYSDIR)/doc/pdf
+ $(INSTALL_DIR) $(RELSYSDIR)/doc/html
+ $(INSTALL_DATA) $(HTMLDIR)/* \
+ $(RELSYSDIR)/doc/html
+ $(INSTALL_DATA) $(INFO_FILE) $(RELSYSDIR)
+ $(INSTALL_DIR) $(RELEASE_PATH)/man/man3
+ $(INSTALL_DATA) $(MAN3DIR)/* $(RELEASE_PATH)/man/man3
+
+release_spec:
+
diff --git a/lib/reltool/doc/src/book.gif b/lib/reltool/doc/src/book.gif
new file mode 100644
index 0000000000..94b3868792
--- /dev/null
+++ b/lib/reltool/doc/src/book.gif
Binary files differ
diff --git a/lib/reltool/doc/src/book.xml b/lib/reltool/doc/src/book.xml
new file mode 100644
index 0000000000..f50d314711
--- /dev/null
+++ b/lib/reltool/doc/src/book.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE book SYSTEM "book.dtd">
+
+<book xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header titlestyle="normal">
+ <copyright>
+ <year>2009</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Reltool</title>
+ <prepared>H&aring;kan Mattsson</prepared>
+ <docno></docno>
+ <date></date>
+ <rev>%VSN%/rev></rev>
+ </header>
+ <insidecover>
+ </insidecover>
+ <pagetext>Reltool</pagetext>
+ <preamble>
+ <contents level="2"></contents>
+ </preamble>
+ <parts lift="no">
+ <xi:include href="part.xml"/>
+ </parts>
+ <applications>
+ <xi:include href="ref_man.xml"/>
+ </applications>
+ <releasenotes>
+ <xi:include href="notes.xml"/>
+ </releasenotes>
+ <index></index>
+</book>
diff --git a/lib/reltool/doc/src/fascicules.xml b/lib/reltool/doc/src/fascicules.xml
new file mode 100644
index 0000000000..a57808974e
--- /dev/null
+++ b/lib/reltool/doc/src/fascicules.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE fascicules SYSTEM "fascicules.dtd">
+
+<fascicules>
+ <fascicule file="part" href="part_frame.html" entry="no">
+ User's Guide
+ </fascicule>
+ <fascicule file="ref_man" href="ref_man_frame.html" entry="yes">
+ Reference Manual
+ </fascicule>
+ <fascicule file="part_notes" href="part_notes_frame.html" entry="no">
+ Release Notes
+ </fascicule>
+ <fascicule file="" href="../../../../doc/print.html" entry="no">
+ Off-Print
+ </fascicule>
+</fascicules>
diff --git a/lib/reltool/doc/src/files.mk b/lib/reltool/doc/src/files.mk
new file mode 100644
index 0000000000..b2dc06411d
--- /dev/null
+++ b/lib/reltool/doc/src/files.mk
@@ -0,0 +1,45 @@
+#-*-makefile-*- ; force emacs to enter makefile-mode
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+
+XML_APPLICATION_FILES = \
+ ref_man.xml
+
+XML_REF3_FILES = \
+ reltool.xml
+
+XML_PART_FILES = \
+ part.xml \
+ part_notes.xml
+
+XML_CHAPTER_FILES = \
+ reltool_intro.xml \
+ reltool_usage.xml \
+ reltool_examples.xml \
+ notes.xml
+
+BOOK_FILES = book.xml
+
+GIF_FILES = \
+ book.gif \
+ note.gif \
+ notes.gif \
+ ref_man.gif \
+ user_guide.gif \
+ warning.gif
+
diff --git a/lib/reltool/doc/src/make.dep b/lib/reltool/doc/src/make.dep
new file mode 100644
index 0000000000..59e77e8162
--- /dev/null
+++ b/lib/reltool/doc/src/make.dep
@@ -0,0 +1,20 @@
+# ----------------------------------------------------
+# >>>> Do not edit this file <<<<
+# This file was automaticly generated by
+# /home/otp/bin/docdepend
+# ----------------------------------------------------
+
+
+# ----------------------------------------------------
+# TeX files that the DVI file depend on
+# ----------------------------------------------------
+
+book.dvi: book.tex part.tex ref_man.tex reltool.tex \
+ reltool_examples.tex reltool_intro.tex reltool_usage.tex
+
+# ----------------------------------------------------
+# Source inlined when transforming from source to LaTeX
+# ----------------------------------------------------
+
+book.tex: ref_man.xml
+
diff --git a/lib/reltool/doc/src/note.gif b/lib/reltool/doc/src/note.gif
new file mode 100644
index 0000000000..6fffe30419
--- /dev/null
+++ b/lib/reltool/doc/src/note.gif
Binary files differ
diff --git a/lib/reltool/doc/src/notes.gif b/lib/reltool/doc/src/notes.gif
new file mode 100644
index 0000000000..e000cca26a
--- /dev/null
+++ b/lib/reltool/doc/src/notes.gif
Binary files differ
diff --git a/lib/reltool/doc/src/notes.xml b/lib/reltool/doc/src/notes.xml
new file mode 100644
index 0000000000..293793e900
--- /dev/null
+++ b/lib/reltool/doc/src/notes.xml
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2009</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Reltool Release Notes</title>
+ <prepared>H&aring;kan Mattsson</prepared>
+ <docno></docno>
+ <date></date>
+ <rev></rev>
+ <file>notes.xml</file>
+ </header>
+ <p>This document describes the changes made to the Reltool system
+ from version to version. The intention of this document is to
+ list all incompatibilities as well as all enhancements and
+ bugfixes for every release of Reltool. Each release of Reltool
+ thus constitutes one section in this document. The title of each
+ section is the version number of Reltool.</p>
+
+
+ <section><title>Reltool 0.5.2</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The documentation is now built with open source tools
+ (xsltproc and fop) that exists on most platforms. One
+ visible change is that the HTML frames are removed.</p>
+ <p>
+ Own Id: OTP-8254</p>
+ </item>
+ </list>
+ </section>
+
+ </section>
+
+ <section><title>Reltool 0.5.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ In the new release of Reltool (0.5) there is a severe bug
+ that may cause your source files to be deleted. The bug
+ is triggered when you generate a target system WITH
+ archive files AND your library directories are given with
+ absolute paths. (Library directories are given with the
+ ERL_LIBS environment variable or with the lib_dirs
+ configuration parameter.)</p>
+ <p>
+ Own Id: OTP-8199</p>
+ </item>
+ </list>
+ </section>
+
+ </section>
+
+ <section><title>Reltool 0.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The tool is still experimental. Feedback is appreciated. </p>
+ <p>
+ Major improvements of the target system generation has
+ been performed. Now it is possible to generate a
+ relocatable target system that does not need to be
+ re-installed if it is moved to a new location. Archive
+ files are automatically generated. A detailed
+ specification of the target system can be generated
+ without actually creating the target system. Relocatable
+ escripts are put in the bin directory of the target
+ system. etc. etc.</p>
+ <p>
+ Some new functions has been introduced in the
+ <c>reltool</c> module: <c>start/1, start_link/1,
+ get_config/3, get_target_spec/1 and
+ eval_target_spec/3</c>.</p>
+ <p>
+ Some new configuration parameters has been introduced:
+ <c>profile, incl_sys_filters, excl_sys_filters,
+ incl_app_filters, excl_app_filters, incl_archive_filters,
+ excl_archive_filters, archive_opts, escript and
+ relocatable</c>.</p>
+ <p>
+ Some parameters have been obsoleted and given new
+ semantics: <c>incl_erts_dirs (incl_sys_filters),
+ excl_erts_dirs (excl_sys_filters), incl_app_dirs
+ (incl_app_filters), excl_app_dirs (excl_app_filters) and
+ escripts (escript).</c></p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-7949</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ The application is experimental. Feedback is appreciated.</p>
+ </item>
+ </list>
+ </section>
+
+ </section>
+
+ <section>
+ <title>Reltool 0.2.2</title>
+
+ <section>
+ <title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The tool crashed when rel, script and boot files were
+ generated via the GUI. This has been fixed.</p>
+ <p>
+ Own Id: OTP-7999</p>
+ </item>
+ </list>
+ </section>
+
+ </section>
+
+ <section>
+ <title>Reltool 0.2.1</title>
+
+ <section>
+ <title>Improvements and New Features</title>
+
+ <list>
+ <item>
+ <p>
+ Minor GUI fixes</p>
+ <p>
+ Own Id: OTP-7840</p>
+ </item>
+ </list>
+ </section>
+
+ </section>
+
+ <section>
+ <title>Reltool 0.2</title>
+ <section>
+ <title>Improvements and new features</title>
+ <p>This is the first (experimental) release of Reltool.</p>
+ </section>
+
+ <section>
+ <title>Known Bugs and Problems</title>
+ <list>
+ <item>
+ <p>
+ The application is experimental.</p>
+ </item>
+ </list>
+ </section>
+ </section>
+</chapter>
diff --git a/lib/reltool/doc/src/part.xml b/lib/reltool/doc/src/part.xml
new file mode 100644
index 0000000000..f78022a718
--- /dev/null
+++ b/lib/reltool/doc/src/part.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE part SYSTEM "part.dtd">
+
+<part xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>2009</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Reltool Users Guide</title>
+ <prepared>H&aring;kan Mattsson</prepared>
+ <docno></docno>
+ <date></date>
+ <rev>%VSN%</rev>
+ </header>
+ <description>
+ <p><c>Reltool</c> is a release management tool. It analyses a given
+ Erlang/OTP installation and determines various dependencies
+ between applications. The <c>graphical</c> frontend depicts the
+ dependencies and enables interactive customization of a
+ target system. The backend provides a <c>batch</c> interface
+ for generation of customized target systems.</p>
+ </description>
+ <xi:include href="reltool_intro.xml"/>
+ <xi:include href="reltool_usage.xml"/>
+ <xi:include href="reltool_examples.xml"/>
+</part>
diff --git a/lib/reltool/doc/src/part_notes.xml b/lib/reltool/doc/src/part_notes.xml
new file mode 100644
index 0000000000..5a2aeecce6
--- /dev/null
+++ b/lib/reltool/doc/src/part_notes.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE part SYSTEM "part.dtd">
+
+<part xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>2009</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Reltool Release Notes</title>
+ <prepared>H&aring;kan Mattsson</prepared>
+ <docno></docno>
+ <date></date>
+ <rev>%VSN%</rev>
+ </header>
+ <description>
+ <p><c>Reltool</c> is a release management tool. It analyses a given
+ Erlang/OTP installation and determines various dependencies
+ between applications. The <c>graphical</c> frontend depicts the
+ dependencies and enables interactive customization of a
+ target system. The backend provides a <c>batch</c> interface
+ for generation of customized target systems.</p>
+ </description>
+ <xi:include href="notes.xml"/>
+</part>
diff --git a/lib/reltool/doc/src/ref_man.gif b/lib/reltool/doc/src/ref_man.gif
new file mode 100644
index 0000000000..b13c4efd53
--- /dev/null
+++ b/lib/reltool/doc/src/ref_man.gif
Binary files differ
diff --git a/lib/reltool/doc/src/ref_man.xml b/lib/reltool/doc/src/ref_man.xml
new file mode 100644
index 0000000000..52602bd069
--- /dev/null
+++ b/lib/reltool/doc/src/ref_man.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE application SYSTEM "application.dtd">
+
+<application xmlns:xi="http://www.w3.org/2001/XInclude">
+ <header>
+ <copyright>
+ <year>2009</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Reltool Reference Manual</title>
+ <prepared>H&aring;kan Mattsson</prepared>
+ <docno></docno>
+ <date></date>
+ <rev>%VSN%</rev>
+ </header>
+ <description>
+ <p><c>Reltool</c> is a release management tool. It analyses a given
+ Erlang/OTP installation and determines various dependencies
+ between applications. The <c>graphical</c> frontend depicts the
+ dependencies and enables interactive customization of a
+ target system. The backend provides a <c>batch</c> interface
+ for generation of customized target systems.</p>
+ </description>
+ <xi:include href="reltool.xml"/>
+</application>
diff --git a/lib/reltool/doc/src/reltool.xml b/lib/reltool/doc/src/reltool.xml
new file mode 100644
index 0000000000..9786928ae8
--- /dev/null
+++ b/lib/reltool/doc/src/reltool.xml
@@ -0,0 +1,698 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE erlref SYSTEM "erlref.dtd">
+
+<erlref>
+ <header>
+ <copyright>
+ <year>2009</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>reltool</title>
+ <prepared>H&aring;kan Mattsson</prepared>
+ <responsible>H&aring;kan Mattsson</responsible>
+ <docno></docno>
+ <approved>H&aring;kan Mattsson</approved>
+ <checked></checked>
+ <date></date>
+ <rev>%VSN%</rev>
+ </header>
+ <module>reltool</module>
+ <modulesummary>Main API of the Reltool application</modulesummary>
+ <description>
+ <p>This is an interface module for the Reltool application.</p>
+
+ <p><em>Reltool</em> is a release management tool. It analyses a
+ given Erlang/OTP installation and determines various dependencies
+ between applications. The <c>graphical</c> frontend depicts the
+ dependencies and enables interactive customization of a target
+ system. The backend provides a <c>batch</c> interface for
+ generation of customized target systems.</p>
+
+ <p>The tool uses an installed Erlang/OTP system as input.
+ <c>root_dir</c> is the root directory of the analysed system and
+ it defaults to the system executing <c>reltool</c>. Applications
+ may also be located outside <c>root_dir</c>. <c>lib_dirs</c>
+ defines additional library directories where applications
+ additional may reside and it defaults to the the directories
+ listed by the operating system environment variable
+ <c>ERL_LIBS</c>. See the module <c>code</c> for more info.
+ Finally single modules and entire applications may be read from
+ Escripts.</p>
+
+ <p>Some configuration parameters control the behavior of Reltool
+ on system (<c>sys</c>) level. Others provide control on
+ application (<c>app</c>) level and yet others are on module
+ (<c>mod</c>) level. Module level parameters overrides application
+ level parameters and application level parameters overrides system
+ level parameters. Escript <c>escript</c> level parameters
+ overrides system level parameters.</p>
+
+ <p>The following top level <c>options</c> are supported:</p>
+
+ <taglist>
+
+ <tag><c><![CDATA[config]]></c></tag>
+ <item>
+ <p>This is the main option and it controls the configuration
+ of <c>reltool</c>. It can either be a <c>sys</c> tuple or
+ a name of a <c>file</c> containing a sys tuple.</p>
+ </item>
+
+ <tag><c><![CDATA[trap_exit]]></c></tag>
+ <item>
+ <p>This option controls the error handling behavior of
+ <c>reltool</c>. By default the window processes traps
+ exit, but this behavior can altered by setting
+ <c>trap_exit</c> to <c>false</c>.</p>
+ </item>
+
+ <tag><c><![CDATA[wx_debug]]></c></tag>
+ <item>
+ <p>This option controls the debug level of <c>wx</c>. As its
+ name indicates it is only useful for debugging. See
+ <c>wx:debug/1</c> for more info.</p>
+ </item>
+
+ </taglist>
+
+ <p>Besides the already mentioned source parameters <c>root_dir</c>
+ and <c>lib_dirs</c>, the following system (<c>sys</c>) level
+ options are supported:</p>
+
+ <taglist>
+
+ <tag><c><![CDATA[erts]]></c></tag>
+ <item>
+ <p>Erts specific configuration. See application level options
+ below.</p>
+ </item>
+
+ <tag><c><![CDATA[escript]]></c></tag>
+ <item>
+ <p>Escript specific configuration. An escript has a mandatory
+ file name and escript level options that are described
+ below.</p>
+ </item>
+
+ <tag><c><![CDATA[app]]></c></tag>
+ <item>
+ <p>Application specific configuration. An application has a
+ mandatory name and application level options that are
+ described below.</p>
+ </item>
+
+ <tag><c><![CDATA[mod_cond]]></c></tag>
+ <item>
+ <p>This parameter controls the module inclusion policy. It
+ defaults to <c>all</c> which means that if an application is
+ included (either explicitly or implicitly) all modules in that
+ application will be included. This implies that both modules
+ that exists on the <c>ebin</c> directory of the application,
+ as well as modules that are named in the <c>app</c> file will
+ be included. If the parameter is set to <c>ebin</c>, both
+ modules on the <c>ebin</c> directory and derived modules are
+ included. If the parameter is set to <c>app</c>, both modules
+ in the <c>app</c> file and derived modules are included.
+ <c>derived</c> means that only modules that are used by other
+ included modules are included. The <c>mod_cond</c> setting on
+ system level is used as default for all applications.</p>
+ </item>
+
+ <tag><c><![CDATA[incl_cond]]></c></tag>
+ <item>
+ <p>This parameter controls the application and escript
+ inclusion policy. It defaults to <c>derived</c> which means
+ that the applications that not have any explicit
+ <c>incl_cond</c> setting, will only be included if any other
+ (explicitly or implicitly included) application uses it. The
+ value <c>include</c> implies that all applications and
+ escripts that that not have any explicit <c>incl_cond</c>
+ setting will be included. <c>exclude</c> implies that all
+ applications and escripts) that that not have any explicit
+ <c>incl_cond</c> setting will be excluded.</p>
+ </item>
+
+ <tag><c><![CDATA[boot_rel]]></c></tag>
+ <item>
+ <p>A target system may have several releases but the one given
+ as <c>boot_rel</c> will be used as default when the system is
+ booting up.</p>
+ </item>
+
+ <tag><c><![CDATA[rel]]></c></tag>
+ <item>
+ <p>Release specific configuration. Each release maps to a
+ <c>rel</c>, <c>script</c> and <c>boot </c> file. See the
+ module <c>systools</c> for more info about the details. Each
+ release has a name, a version and a set of applications with a
+ few release specific parameters such as type and included
+ applications.</p>
+ </item>
+
+ <tag><c><![CDATA[relocatable]]></c></tag>
+ <item>
+ <p>This parameter controls whether the <c>erl</c> executable
+ in the target system automatically should determine where it
+ is installed or if it should use a hardcoded path to the
+ installation. In the latter case the target system must be
+ installed with <c>reltool:install/2</c> before it can be
+ used. If the system is relocatable, the file tree containing
+ the target system can be moved to another location without
+ re-installation. The default is <c>true</c>.</p>
+ </item>
+
+ <tag><c><![CDATA[profile]]></c></tag>
+ <item>
+ <p>The creation of the specification for a target system is
+ performed in two steps. In the first step a complete
+ specification is generated. It will likely contain much more
+ files than you are interested in your customized target
+ system. In the second step the specification will be filtered
+ according to your filters. There you have the ability to
+ specify filters per application as well as system wide
+ filters. You can also select a <c>profile</c> for your
+ system. Depending on the <c>profile</c>, different default
+ filters will be used. There are three different profiles to
+ choose from: <c>development</c>, <c>embedded</c> and
+ <c>standalone</c>. <c>development</c> is default. The
+ parameters that are affected by the <c>profile</c> are:
+ <c>incl_sys_filters</c>, <c>excl_sys_filters</c>,
+ <c>incl_app_filters</c> and <c>excl_app_filters</c>.</p>
+ </item>
+
+ <tag><c><![CDATA[app_file]]></c></tag>
+ <item>
+ <p>This parameter controls the default handling of the
+ <c>app</c> files when a target system is generated. It
+ defaults to <c>keep</c> which means that <c>app</c> files are
+ copied to the target system and their contents are kept as
+ they are. <c>strip</c> means that a new <c>app</c> file is
+ generated from the contents of the original <c>app</c> file
+ where the non included modules are removed from the
+ file. <c>all</c> does also imply that a new <c>app</c> file is
+ generated from the contents of the original <c>app</c> file,
+ with the difference that all included modules are added to the
+ file. If the application does not have any <c>app</c> file a
+ file will be created for <c>all</c> but not for <c>keep</c>
+ and <c>strip</c>.</p>
+ </item>
+
+ <tag><c><![CDATA[debug_info]]></c></tag>
+ <item>
+ <p>The <c>debug_info</c> parameter controls whether the debug
+ information in the beam file should be kept (<c>keep</c>) or
+ stripped <c>strip</c> when the file is copied to the target
+ system.</p>
+ </item>
+
+ <tag><c><![CDATA[incl_sys_filters]]></c></tag>
+ <item>
+ <p>This parameter normally contains a list of regular
+ expressions that controls which files in the system that
+ should be included. Each file in the target system must match
+ at least one of the listed regular expressions in order to be
+ included. Further the files may not match any filter in
+ <c>excl_sys_filters</c> in order to be included. Which
+ application files that should be included are controlled with
+ the parameters <c>incl_app_filters</c> and
+ <c>excl_app_filters</c>. This parameter defaults to
+ <c>[".*"]</c>.</p>
+ </item>
+
+ <tag><c><![CDATA[excl_sys_filters]]></c></tag>
+ <item>
+ <p>This parameter normally contains a list of regular
+ expressions that controls which files in the system that not
+ should be included in the target system. In order to be
+ included, a file must match some filter in
+ <c>incl_sys_filters</c> but not any filter in
+ <c>excl_sys_filters</c>. This parameter defaults to
+ <c>[]</c>.</p>
+ </item>
+ <tag><c><![CDATA[incl_app_filters]]></c></tag>
+ <item>
+ <p>This parameter normally contains a list of regular
+ expressions that controls which application specific files
+ that should be included. Each file in the application must
+ match at least one of the listed regular expressions in order
+ to be included. Further the files may not match any filter in
+ <c>excl_app_filters</c> in order to be included. This
+ parameter defaults to <c>[".*"]</c>.</p>
+ </item>
+
+ <tag><c><![CDATA[excl_app_filters]]></c></tag>
+ <item>
+ <p>This parameter normally contains a list of regular
+ expressions that controls which application specific files
+ that not should be included in the target system. In order to
+ be included, a file must match some filter in
+ <c>incl_app_filters</c> but not any filter in
+ <c>excl_app_filters</c>. This parameter defaults to
+ <c>[]</c>.</p>
+ </item>
+
+ <tag><c><![CDATA[incl_archive_filters]]></c></tag>
+ <item>
+ <p>This parameter normally contains a list of regular
+ expressions that controls which top level directories in an
+ application that should be included in an archive file (as
+ opposed of beeing included as a regular directory outside the
+ archive). Each top directory in the application must match at
+ least one of the listed regular expressions in order to be
+ included. Further the files may not match any filter in
+ <c>excl_app_filters</c> in order to be included. This
+ parameter defaults to <c>[".*"]</c>.</p>
+ </item>
+
+ <tag><c><![CDATA[excl_archive_filters]]></c></tag>
+ <item>
+ <p>This parameter normally contains a list of regular
+ expressions that controls which top level directories in an
+ application that not should be included in an archive file. In
+ order to be included in the application archive, a top
+ directory must match some filter in <c>incl_archive_filters</c>
+ but not any filter in <c>excl_archive_filters</c>. This
+ parameter defaults to <c>["^include$","^priv$"]</c>.</p>
+ </item>
+
+ <tag><c><![CDATA[archive_opts]]></c></tag>
+ <item>
+ <p>This parameter contains a list of options that are given to
+ <c>zip:create/3</c> when application specific files are
+ packaged into an archive. All options are not supported. The
+ most useful options in this context, are the ones that
+ controls which types of files that should be compressed. This
+ parameter defaults to <c>[]</c>.</p>
+ </item>
+
+ </taglist>
+
+ <p>On application (<c>escript</c>) level,the following options are
+ supported:</p>
+
+ <taglist>
+ <tag><c><![CDATA[incl_cond]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+ </taglist>
+
+ <p>On application (<c>app</c>) level,the following options are
+ supported:</p>
+
+ <taglist>
+ <tag><c><![CDATA[vsn]]></c></tag>
+ <item>
+ <p>The version of the application. In an installed system there may
+ exist several versions of an application. The <c>vsn</c> parameter
+ controls which version of the application that will be choosen. If it
+ is omitted, the latest version will be choosen.</p>
+ </item>
+ <tag><c><![CDATA[mod]]></c></tag>
+ <item>
+ <p>Module specific configuration. A module has a mandatory
+ name and module level options that are described below.</p>
+ </item>
+ <tag><c><![CDATA[mod_cond]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+ <tag><c><![CDATA[incl_cond]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+ <tag><c><![CDATA[app_file]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+ <tag><c><![CDATA[debug_info]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+ <tag><c><![CDATA[incl_app_filters]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+ <tag><c><![CDATA[excl_app_filters]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+
+ <tag><c><![CDATA[incl_archive_filters]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+
+ <tag><c><![CDATA[excl_archive_filters]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+
+ <tag><c><![CDATA[archive_opts]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with the
+ same name on system level.</p>
+ </item>
+ </taglist>
+
+ <p>On module (<c>mod</c>) level,the following options are
+ supported:</p>
+
+ <taglist>
+ <tag><c><![CDATA[incl_cond]]></c></tag>
+ <item>
+ <p>This parameter controls whether the module is included or not. By
+ default the <c>mod_incl</c> parameter on application and system level
+ will be used to control whether the module is included or not. The
+ value of <c>incl_cond</c> overrides the module inclusion policy.
+ <c>include</c> implies that the module is included, while
+ <c>exclude</c> implies that the module not is included.
+ <c>derived</c> implies that the is included if any included uses the
+ module.</p>
+ </item>
+ <tag><c><![CDATA[debug_info]]></c></tag>
+ <item>
+ <p>The value of this parameter overrides the parameter with
+ the same name on application level.</p>
+ </item>
+ </taglist>
+
+ </description>
+
+ <section>
+ <title>DATA TYPES</title>
+ <code type="none"><![CDATA[
+options() = [option()]
+option() = {config, config() | file()}
+ | {trap_exit, bool()}
+ | {wx_debug, term()}
+config() = {sys, [sys()]}
+sys() = {root_dir, root_dir()}
+ | {lib_dirs, [lib_dir()]}
+ | {profile, profile()}
+ | {erts, app()}
+ | {escript, escript_file(), [escript()]}
+ | {app, app_name(), [app()]}
+ | {mod_cond, mod_cond()}
+ | {incl_cond, incl_cond()}
+ | {boot_rel, boot_rel()}
+ | {rel, rel_name(), rel_vsn(), [rel_app()]}
+ | {relocatable, relocatable()}
+ | {app_file, app_file()}
+ | {debug_info, debug_info()}
+ | {incl_sys_filters, incl_sys_filters()}
+ | {excl_sys_filters, excl_sys_filters()}
+ | {incl_app_filters, incl_app_filters()}
+ | {excl_app_filters, excl_app_filters()}
+ | {incl_archive_filters, incl_archive_filters()}
+ | {excl_archive_filters, excl_archive_filters()}
+ | {archive_opts, [archive_opt()]}
+app() = {vsn, app_vsn()}
+ | {mod, mod_name(), mod()}
+ | {mod_cond, mod_cond()}
+ | {incl_cond, incl_cond()}
+ | {debug_info, debug_info()}
+ | {app_file, app_file()}
+ | {incl_sys_filters, incl_sys_filters()}
+ | {excl_sys_filters, excl_sys_filters()}
+ | {incl_app_filters, incl_app_filters()}
+ | {excl_app_filters, excl_app_filters()}
+ | {incl_archive_filters, incl_archive_filters()}
+ | {excl_archive_filters, excl_archive_filters()}
+ | {archive_opts, [archive_opt()]}
+mod() = {vsn, app_vsn()}
+ | {incl_cond, incl_cond()}
+ | {debug_info, debug_info()}
+rel_app() = app_name()
+ | {app_name(), app_type()}
+ | {app_name(), [incl_app()]}
+ | {app_name(), app_type(), [incl_app()]}
+app_name() = atom()
+app_type() = permanent | transient | temporary | load | none
+app_vsn() = string()
+archive_opt = zip_create_opt()
+boot_rel() = rel_name()
+app_file() = keep | strip | all
+debug_info() = keep | strip
+dir() = string()
+escript() = {incl_cond, incl_cond()}
+escript_file() = file()
+excl_app_filters() = regexps()
+excl_archive_filters() = regexps()
+excl_sys_filters() = regexps()
+file() = string()
+incl_app() = app_name()
+incl_app_filters() = regexps()
+incl_archive_filters() = regexps()
+incl_cond() = include | exclude | derived
+incl_sys_filters() = regexps()
+lib_dir() = dir()
+mod_cond() = all | app | ebin | derived | none
+mod_name() = atom()
+profile() = development | embedded | standalone
+re_regexp() = string()
+reason() = string()
+regexps() = [re_regexp()] | {add, [re_regexp()]} | {del, [re_regexp()]}
+rel_file() = term()
+rel_name() = string()
+rel_vsn() = string()
+relocatable = boolean()
+root_dir() = dir()
+script_file() = term()
+server() = server_pid() | options()
+server_pid() = pid()
+target_dir() = file()
+window_pid() = pid()]]></code>
+
+ <marker id="start"></marker>
+ </section>
+
+ <funcs>
+ <func>
+ <name>create_target(Server, TargetDir) -> ok | {error, Reason}</name>
+ <fsummary>Create a target system</fsummary>
+ <type>
+ <v>Server = server()</v>
+ <v>TargetDir = target_dir()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Create a target system. Gives the same result as
+ <c>{ok,TargetSpec}=reltool:get_target_spec(Server)</c> and
+ <c>reltool:eval_target_spec(TargetSpec,RootDir,TargetDir)</c>.</p></desc>
+ </func>
+
+ <func>
+ <name>eval_target_spec(TargetSpec, RootDir, TargetDir) -> ok | {error, Reason}</name>
+ <fsummary>Create a target system</fsummary>
+ <type>
+ <v>TargetSpec = target_spec()</v>
+ <v>RootDir = root_dir()</v>
+ <v>TargetDir = target_dir()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Create the actual target system from a specification
+ generated by <c>reltool:get_target_spec/1</c>. The creation of
+ the specification for a target system is performed in two
+ steps. In the first step a complete specification will be
+ generated. It will likely contain much more files than you are
+ interested in in your target system. In the second step the
+ specification will be filtered according to your filters. There
+ you have the ability to specify filters per application as well
+ as system wide filters. You can also select a <c>profile</c> for
+ your system. Depending on the <c>profile</c>, different default
+ filters will be used.</p>
+
+ <p>The top directories <c>bin</c>, <c>releases</c> and
+ <c>lib</c> are treated differently from other files. All other
+ files are by default copied to the target system. The
+ <c>releases</c> directory contains generated <c>rel</c>,
+ <c>script</c>, and <c>boot</c> files. The <c>lib</c> directory
+ contains the applications. Which applications that are included
+ and if they should be customized (archived, stripped from debug
+ info etc.) is specified with various configuration
+ parameters. The files in the <c>bin</c> directory are copied
+ from the <c>erts-vsn/bin</c> directory, but only those files
+ that was originally included in <c>bin</c> directory of the
+ source system.</p>
+
+ <p>If the configuration parameter <c>relocatable</c> was set to
+ <c>true</c> there is no need to install the target system with
+ <c>reltool:install/2</c> before it can be started. In that case
+ the file tree containing the target system can be moved without
+ re-installation.</p></desc>
+ </func>
+
+ <func>
+ <name>get_config(Server) -> {ok, Config} | {error, Reason}</name>
+ <fsummary>Get reltool configuration</fsummary>
+ <type>
+ <v>Server = server()</v>
+ <v>Config = config()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Get reltool configuration. Shorthand for
+ <c>reltool:get_config(Server,false,false)</c>.</p></desc>
+ </func>
+
+ <func>
+ <name>get_config(Server, InclDefaults, InclDerived) -> {ok, Config} | {error, Reason}</name>
+ <fsummary>Get reltool configuration</fsummary>
+ <type>
+ <v>Server = server()</v>
+ <v>InclDefaults = incl_defaults()</v>
+ <v>InclDerived = incl_derived()</v>
+ <v>Config = config()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Get reltool configuration. Normally, only the explicit
+ configuration parameters with values that differs from their
+ defaults are interesting. But the builtin default values can be
+ returned by setting <c>InclDefaults</c> to <c>true</c>. The
+ derived configuration can be return by setting
+ <c>InclDerived</c> to <c>true</c>.</p></desc>
+ </func>
+
+ <func>
+ <name>get_rel(Server, Relname) -> {ok, RelFile} | {error, Reason}</name>
+ <fsummary>Get contents of a release file</fsummary>
+ <type>
+ <v>Server = server()</v>
+ <v>RelName = rel_name()</v>
+ <v>RelFile = rel_file()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Get contents of a release file. See <c>rel(4)</c> for more
+ details.</p></desc>
+ </func>
+
+ <func>
+ <name>get_script(Server, Relname) -> {ok, ScriptFile | {error, Reason}</name>
+ <fsummary>Get contents of a boot script file</fsummary>
+ <type>
+ <v>Server = server()</v>
+ <v>RelName = rel_name()</v>
+ <v>ScriptFile = script_file()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Get contents of a boot script file. See <c>script(4)</c> for
+ more details.</p></desc>
+ </func>
+
+ <func>
+ <name>get_server(WindowPid) -> {ok, ServerPid} | {error, Reason}</name>
+ <fsummary>Start server process with options</fsummary>
+ <type>
+ <v>WindowPid = window_pid()</v>
+ <v>ServerPid = server_pid()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Return the process identifier of the server process.</p></desc>
+ </func>
+
+ <func>
+ <name>get_target_spec(Server) -> {ok, targetSpec} | {error, Reason}</name>
+ <fsummary>Return a specification of the target system</fsummary>
+ <type>
+ <v>Server = server()</v>
+ <v>TargetSpec = target_spec()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Return a specification of the target system. The actual
+ target system can be created with
+ <c>reltool:eval_target_spec/3</c>.</p>
+ </desc>
+ </func>
+
+ <func>
+ <name>install(Server, TargetDir) -> ok | {error, Reason}</name>
+ <fsummary>Install a target system</fsummary>
+ <type>
+ <v>Server = server()</v>
+ <v>TargetDir = target_dir()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Install a created target system</p></desc>
+ </func>
+
+ <func>
+ <name>start() -> {ok, WindowPid} | {error, Reason}</name>
+ <fsummary>Start main window process</fsummary>
+ <type>
+ <v>WindowPid = window_pid()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Start a main window process with default options</p></desc>
+ </func>
+
+ <func>
+ <name>start(Options) -> {ok, WindowPid} | {error, Reason}</name>
+ <fsummary>Start main window process with options</fsummary>
+ <type>
+ <v>Options = options()</v>
+ <v>WindowPid = window_pid()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Start a main window process with options</p></desc>
+ </func>
+
+ <func>
+ <name>start_link(Options) -> {ok, WindowPid} | {error, Reason}</name>
+ <fsummary>Start main window process with options</fsummary>
+ <type>
+ <v>Options = options()</v>
+ <v>WindowPid = window_pid()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Start a main window process with options. The process is linked.</p></desc>
+ </func>
+
+ <func>
+ <name>start_server(Options) -> {ok, ServerPid} | {error, Reason}</name>
+ <fsummary>Start server process with options</fsummary>
+ <type>
+ <v>Options = options()</v>
+ <v>ServerPid = server_pid()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Start a server process with options. The server process
+ identity can be given as argument to several other functions in the
+ API.</p></desc>
+ </func>
+
+ <func>
+ <name>stop(Pid) -> ok | {error, Reason}</name>
+ <fsummary>Stop a server or window process</fsummary>
+ <type>
+ <v>Pid = server_pid() | window_pid()()</v>
+ <v>Reason = reason()</v>
+ </type>
+ <desc><p>Stop a server or window process</p></desc>
+ </func>
+ </funcs>
+
+</erlref>
diff --git a/lib/reltool/doc/src/reltool_examples.xml b/lib/reltool/doc/src/reltool_examples.xml
new file mode 100644
index 0000000000..3d087862e6
--- /dev/null
+++ b/lib/reltool/doc/src/reltool_examples.xml
@@ -0,0 +1,466 @@
+<?xml version="1.0" encoding="latin1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2009</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may noot use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Examples</title>
+ <prepared>H&aring;kan Mattsson</prepared>
+ <responsible>H&aring;kan Mattsson</responsible>
+ <docno></docno>
+ <approved>H&aring;kan Mattsson</approved>
+ <checked></checked>
+ <date></date>
+ <rev>%VSN%</rev>
+ <file>reltool_examples.xml</file>
+ </header>
+
+ <section>
+ <title>Start and stop windows and servers</title>
+
+ <p>The main process in Reltool is the server. It can be used as it
+ is or be used via the GUI frontend process. When the GUI is
+ started, a server process will automatically be started. The GUI
+ process is started with <c>reltool:start/0</c>,
+ <c>reltool:start/1</c> or <c>reltool:start_link/1</c>. The pid of
+ its server can be obtained with <c>reltool:get_server/1</c></p>
+
+ <pre>
+Erlang R13B02 (erts-5.7.3) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
+
+Eshell V5.7.3 (abort with ^G)
+1&gt; {ok, Win} = reltool:start([]).
+{ok,&lt;0.36.01&gt;}
+2&gt; {ok, Server} = reltool:get_server([]).
+{ok,&lt;0.37.01&gt;}
+3&gt; reltool:get_config(Server).
+{ok,{sys,[]}}
+4&gt; reltool:stop(Win).
+ok
+
+
+5&gt; {ok, Server2} = reltool:start_server([]).
+{ok,&lt;0.6535.01&gt;}
+6&gt; reltool:get_config(Server2).
+{ok,{sys,[]}}
+7&gt; reltool:stop(Server2).
+ok
+ </pre>
+
+ </section>
+
+ <section>
+ <title>Inspecting the configuration</title>
+
+ <pre>
+Erlang R13B02 (erts-5.7.3) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
+
+Eshell V5.7.3 (abort with ^G)
+1&gt; Config = {sys, [{escript, "examples/display_args", [{incl_cond, include}]},
+ {app, inets, [{incl_cond, include}]},
+ {app, mnesia, [{incl_cond, exclude}]},
+ {app, ssl, [{incl_cond, exclude}]},
+ {app, runtime_tools, [{incl_cond, exclude}]},
+ {app, syntax_tools, [{incl_cond, exclude}]}]}.
+{sys,[{escript,"examples/display_args",[{incl_cond,include}]},
+ {app,inets,[{incl_cond,include}]},
+ {app,mnesia,[{incl_cond,exclude}]},
+ {app,ssl,[{incl_cond,exclude}]},
+ {app,runtime_tools,[{incl_cond,exclude}]},
+ {app,syntax_tools,[{incl_cond,exclude}]}]}
+
+
+
+2&gt; {ok, Server} = reltool:start_server([Config]).
+{ok,&lt;0.35.0&gt;}
+3&gt; reltool:get_config(Server).
+{ok,{sys,[{escript,"/clearcase/otp/tools/reltool/examples/display_args",
+ [{incl_cond,include}]}]}}
+4&gt; reltool:get_config(Server, false, false).
+{ok,{sys,[{escript,"/clearcase/otp/tools/reltool/examples/display_args",
+ [{incl_cond,include}]}]}}
+
+
+
+5&gt; reltool:get_config(Server, true, false).
+{ok,{sys,[{root_dir,"/ldisk/hakan/otp_test"},
+ {lib_dirs,[]},
+ {escript,"/clearcase/otp/tools/reltool/examples/display_args",
+ [{incl_cond,include}]},
+ {mod_cond,all},
+ {incl_cond,derived},
+ {boot_rel,"start_clean"},
+ {emu_name,"beam"},
+ {relocatable,true},
+ {profile,development},
+ {incl_sys_files,[".*"]},
+ {excl_sys_files,[]},
+ {incl_app_files,[".*"]},
+ {excl_app_files,[]},
+ {incl_archive_dirs,[".*"]},
+ {excl_archive_dirs,["^include$","^priv$"]},
+ {archive_opts,[]},
+ {app_type,permanent},
+ {app_file,keep},
+ {debug_info,keep}]}}
+
+
+
+6&gt; reltool:get_config(Server, true, true).
+{ok,{sys,[{root_dir,"/ldisk/hakan/otp_test"},
+ {lib_dirs,[]},
+ {escript,"/clearcase/otp/tools/reltool/examples/display_args",
+ [{incl_cond,include}]},
+ {mod_cond,all},
+ {incl_cond,derived},
+ {erts,[{vsn,"5.7.3"},
+ {mod,erl_prim_loader,[]},
+ {mod,erlang,[]},
+ {mod,init,[]},
+ {mod,otp_ring0,[]},
+ {mod,prim_file,[]},
+ {mod,prim_inet,[]},
+ {mod,prim_zip,[]},
+ {mod,zlib,[]}]},
+ {app,compiler,
+ [{vsn,"4.6.3"},
+ {mod,beam_asm,[]},
+ {mod,beam_block,[]},
+ {mod,beam_bool,[]},
+ {mod,beam_bsm,[]},
+ {mod,beam_clean,[]},
+ {mod,beam_dead,[]},
+ {mod,beam_dict,[]},
+ {mod,beam_disasm,[]},
+ {mod,beam_flatten,[]},
+ {mod,beam_jump,[]},
+ {mod,beam_listing,[]},
+ {mod,beam_opcodes,...},
+ {mod,...},
+ {...}|...]},
+ {app,crypto,
+ [{vsn,"1.6.1"},
+ {mod,crypto,[]},
+ {mod,crypto_app,[]},
+ {mod,crypto_server,[]},
+ {mod,crypto_sup,[]}]},
+ {app,hipe,
+ [{vsn,"3.7.3"},
+ {mod,cerl_cconv,[]},
+ {mod,cerl_closurean,[]},
+ {mod,cerl_hipeify,[]},
+ {mod,cerl_hybrid_transform,[]},
+ {mod,cerl_lib,[]},
+ {mod,cerl_messagean,[]},
+ {mod,cerl_pmatch,[]},
+ {mod,cerl_prettypr,[]},
+ {mod,cerl_to_icode,[]},
+ {mod,cerl_typean,...},
+ {mod,...},
+ {...}|...]},
+ {app,kernel,
+ [{vsn,"2.13.3"},
+ {mod,application,[]},
+ {mod,application_controller,[]},
+ {mod,application_master,[]},
+ {mod,application_starter,[]},
+ {mod,auth,[]},
+ {mod,code,[]},
+ {mod,code_server,[]},
+ {mod,disk_log,[]},
+ {mod,disk_log_1,...},
+ {mod,...},
+ {...}|...]},
+ {app,stdlib,
+ [{vsn,"1.16.3"},
+ {mod,array,[]},
+ {mod,base64,[]},
+ {mod,beam_lib,[]},
+ {mod,c,[]},
+ {mod,calendar,[]},
+ {mod,dets,[]},
+ {mod,dets_server,[]},
+ {mod,dets_sup,...},
+ {mod,...},
+ {...}|...]},
+ {boot_rel,"start_clean"},
+ {emu_name,"beam"},
+ {relocatable,true},
+ {profile,development},
+ {incl_sys_files,[".*"]},
+ {excl_sys_files,[]},
+ {incl_app_files,[".*"]},
+ {excl_app_files,[]},
+ {incl_archive_dirs,[".*"]},
+ {excl_archive_dirs,["^include$",[...]]},
+ {archive_opts,[]},
+ {app_type,permanent},
+ {app_file,...},
+ {...}]}}
+
+
+
+7&gt; reltool:get_config([{sys,[{profile, embedded}]}]).
+{ok,{sys,[{profile,embedded},
+ {incl_sys_filters,["^bin","^erts","^lib","^releases"]},
+ {excl_sys_filters,["^bin/(erlc|dialyzer|typer)(|\\.exe)$",
+ "^erts.*/bin/(erlc|dialyzer|typer)(|\\.exe)$",
+ "^erts.*/bin/.*(debug|pdb)"]},
+ {incl_app_filters,["^ebin","^include","^priv"]}]}}
+8&gt; reltool:get_config([{sys,[{profile, standalone}]}]).
+{ok,{sys,[{profile,standalone},
+ {incl_sys_filters,["^bin/(erl|epmd)(|\\.exe|\\.ini)$",
+ "^bin/start(|_clean).boot$","^erts.*/bin","^lib$"]},
+ {excl_sys_filters,["^erts.*/bin/(erlc|dialyzer|typer)(|\\.exe)$",
+ "^erts.*/bin/(start|escript|to_erl|run_erl)(|\\.exe)$",
+ "^erts.*/bin/.*(debug|pdb)"]},
+ {incl_app_filters,["^ebin","^priv"]},
+ {excl_app_filters,["^ebin/.*\\.appup$"]}]}}
+
+ </pre>
+
+ </section>
+
+ <section>
+ <title>Generate release and script files</title>
+ <pre>
+5&gt; {ok, Server} = reltool:start_server([{config, {sys, [{boot_rel, "NAME"},
+ {rel, "NAME", "VSN", [kernel, stdlib, sasl]}]}}]).
+{ok,&lt;0.1288.0&gt;}
+6&gt; reltool:get_config(Server).
+{ok,{sys,[{boot_rel,"NAME"},
+ {rel,"NAME","VSN",[kernel,stdlib,sasl]}]}}
+7&gt; reltool:get_rel(Server, "NAME").
+{ok,{release,{"NAME","VSN"},
+ {erts,"5.7"},
+ [{kernel,"2.13"},{stdlib,"1.16"},{sasl,"2.1.6"}]}}
+8&gt; reltool:get_script(Server, "NAME").
+{ok,{script,{"NAME","VSN"},
+ [{preLoaded,[erl_prim_loader,erlang,init,otp_ring0,
+ prim_file,prim_inet,prim_zip,zlib]},
+ {progress,preloaded},
+ {path,["$ROOT/lib/kernel-2.13/ebin",
+ "$ROOT/lib/stdlib-1.16/ebin"]},
+ {primLoad,[error_handler]},
+ {kernel_load_completed},
+ {progress,kernel_load_completed},
+ {path,["$ROOT/lib/kernel-2.13/ebin"]},
+ {primLoad,[application,application_controller,
+ application_master,application_starter,auth,code,
+ code_server,disk_log,disk_log_1,disk_log_server,
+ disk_log_sup,dist_ac,dist_util,erl_boot_server|...]},
+ {path,["$ROOT/lib/stdlib-1.16/ebin"]},
+ {primLoad,[array,base64,beam_lib,c,calendar,dets,
+ dets_server,dets_sup,dets_utils,dets_v8,dets_v9,dict|...]},
+ {path,["$ROOT/lib/sasl-2.1.6/ebin"]},
+ {primLoad,[alarm_handler,erlsrv,format_lib_supp,misc_supp,
+ overload,rb,rb_format_supp,release_handler,
+ release_handler_1,sasl|...]},
+ {progress,modules_loaded},
+ {path,["$ROOT/lib/kernel-2.13/ebin",
+ "$ROOT/lib/stdlib-1.16/ebin","$ROOT/lib/sasl-2.1.6/ebin"]},
+ {kernelProcess,heart,{heart,start,[]}},
+ {kernelProcess,error_logger,{error_logger,start_link,[]}},
+ {kernelProcess,application_controller,
+ {application_controller,start,[{...}]}},
+ {progress,init_kernel_started},
+ {apply,{application,load,[...]}},
+ {apply,{application,load,...}},
+ {progress,applications_loaded},
+ {apply,{...}},
+ {apply,...},
+ {...}|...]}}
+9&gt; reltool:stop(Server).
+ok
+ </pre>
+ </section>
+
+ <section>
+ <title>Create a target system</title>
+ <pre>
+Erlang R13B02 (erts-5.7.3) [source] [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
+
+Eshell V5.7.3 (abort with ^G)
+1&gt; Config = {sys, [{escript, "examples/display_args", [{incl_cond, include}]},
+ {app, inets, [{incl_cond, include}]},
+ {app, mnesia, [{incl_cond, exclude}]},
+ {app, ssl, [{incl_cond, exclude}]},
+ {app, runtime_tools, [{incl_cond, exclude}]},
+ {app, syntax_tools, [{incl_cond, exclude}]}]}.
+{sys,[{escript,"examples/display_args",[{incl_cond,include}]},
+ {app,inets,[{incl_cond,include}]},
+ {app,mnesia,[{incl_cond,exclude}]},
+ {app,ssl,[{incl_cond,exclude}]},
+ {app,runtime_tools,[{incl_cond,exclude}]},
+ {app,syntax_tools,[{incl_cond,exclude}]}]}
+
+
+
+2&gt; {ok, Spec} = reltool:get_target_spec([Config]).
+{ok,[{create_dir,"releases",
+ [{write_file,"start_erl.data","5.7.3 1.0"},
+ {create_dir,"1.0",
+ [{write_file,"start_clean.rel",
+ [37,37,32,114,101,108,32,103,101,110,101,114,97,116|...]},
+ {write_file,"start_clean.script",
+ [37,37,32,115,99,114,105,112,116,32,103,101,110|...]},
+ {write_file,"start_clean.boot",
+ &lt;&lt;131,104,3,100,0,6,115,99,114,105,112,116,...&gt;&gt;},
+ {write_file,"start_sasl.rel",
+ [37,37,32,114,101,108,32,103,101,110,101|...]},
+ {write_file,"start_sasl.script",
+ [37,37,32,115,99,114,105,112,116,32|...]},
+ {write_file,"start_sasl.boot",
+ &lt;&lt;131,104,3,100,0,6,115,99,114,...&gt;&gt;}]}]},
+ {create_dir,"bin",
+ [{copy_file,"display_args.escript",
+ "/clearcase/otp/tools/reltool/examples/display_args"},
+ {copy_file,"display_args","erts-5.7.3/bin/escript"},
+ {copy_file,"start","erts-5.7.3/bin/start"},
+ {copy_file,"erl","erts-5.7.3/bin/dyn_erl"},
+ {copy_file,"epmd","erts-5.7.3/bin/epmd"},
+ {copy_file,"to_erl","erts-5.7.3/bin/to_erl"},
+ {copy_file,"run_erl","erts-5.7.3/bin/run_erl"},
+ {copy_file,"escript","erts-5.7.3/bin/escript"},
+ {copy_file,"erlc","erts-5.7.3/bin/erlc"},
+ {copy_file,"dialyzer","erts-5.7.3/bin/dialyzer"},
+ {copy_file,"typer","erts-5.7.3/bin/typer"},
+ {write_file,"start_clean.boot",
+ &lt;&lt;131,104,3,100,0,6,115,...&gt;&gt;},
+ {write_file,"start_sasl.boot",&lt;&lt;131,104,3,100,0,6,...&gt;&gt;},
+ {write_file,"start.boot",&lt;&lt;131,104,3,100,0,...&gt;&gt;}]},
+ {create_dir,"misc",
+ [{copy_file,"makewhatis"},{copy_file,"format_man_pages"}]},
+ {copy_file,"Install"},
+ {create_dir,"usr",
+ [{create_dir,"lib",
+ [{copy_file,"liberts_r.a"},{copy_file,"liberts.a"}]},
+ {create_dir,"include",
+ [{copy_file,"erl_fixed_size_int_types.h"},
+ {copy_file,"erl_int_sizes_config.h"},
+ {copy_file,"erl_memory_trace_parser.h"},
+ {create_dir,"obsolete",[{copy_file,"driver.h"}]},
+ {copy_file,"driver_int.h"},
+ {copy_file,"erl_driver.h"}]}]},
+ {create_dir,"erts-5.7.3",
+ [{create_dir,"lib",
+ [{create_dir,"internal",
+ [{copy_file,"liberts_internal_r.a"},
+ {copy_file,"liberts_internal.a"},
+ {copy_file,"libethread.a"},
+ {copy_file,"README"}]},
+ {copy_file,"liberts_r.a"},
+ {copy_file,"liberts.a"}]},
+ {create_dir,"bin",
+ [{copy_file,"start"},
+ {copy_file,"erl","erts-5.7.3/bin/dyn_erl"},
+ {copy_file,"epmd"},
+ {copy_file,"to_erl"},
+ {copy_file,"run_erl"},
+ {copy_file,"escript"},
+ {copy_file,"erlc"},
+ {copy_file,"dialyzer"},
+ {copy_file,"typer"},
+ {copy_file,"erlexec"},
+ {copy_file,[...]},
+ {copy_file,...},
+ {...}|...]},
+ {create_dir,"doc",[]},
+ {create_dir,"man",[]},
+ {create_dir,"include",
+ [{create_dir,"internal",
+ [{create_dir,"tile",[{copy_file,...},{...}]},
+ {create_dir,"sparc64",[{...}]},
+ {create_dir,"sparc32",[...]},
+ {create_dir,[...],...},
+ {create_dir,...},
+ {...}|...]},
+ {copy_file,"erl_fixed_size_int_types.h"},
+ {copy_file,"erl_int_sizes_config.h"},
+ {copy_file,"erl_memory_trace_parser.h"},
+ {copy_file,"driver_int.h"},
+ {copy_file,"erl_driver.h"}]},
+ {create_dir,"src",[{copy_file,"setuid_socket_wrap.c"}]}]},
+ {create_dir,"lib",
+ [{archive,"compiler-4.6.3.ez",[],
+ [{create_dir,"compiler-4.6.3",
+ [{create_dir,"ebin",
+ [{copy_file,"compiler.appup"},
+ {copy_file,[...]},
+ {copy_file,...},
+ {...}|...]},
+ {create_dir,"src",
+ [{copy_file,[...]},{copy_file,...},{...}|...]}]}]},
+ {archive,"crypto-1.6.1.ez",[],
+ [{create_dir,"crypto-1.6.1",
+ [{create_dir,"ebin",
+ [{copy_file,[...]},{copy_file,...},{...}|...]},
+ {create_dir,"src",[{copy_file,...},{...}|...]}]}]},
+ {create_dir,"crypto-1.6.1",
+ [{create_dir,"priv",
+ [{create_dir,"lib",[{copy_file,[...]}]},
+ {create_dir,"obj",[{copy_file,...},{...}]}]}]},
+ {archive,"erts-5.7.3.ez",[],
+ [{create_dir,"erts-5.7.3",
+ [{create_dir,"ebin",[{...}|...]},
+ {create_dir,"src",[...]}]}]},
+ {archive,"hipe-3.7.3.ez",[],
+ [{create_dir,"hipe-3.7.3",
+ [{create_dir,"util",[...]},
+ {create_dir,[...],...},
+ {create_dir,...},
+ {...}|...]}]},
+ {archive,"kernel-2.13.3.ez",[],
+ [{create_dir,"kernel-2.13.3",
+ [{create_dir,[...],...},{create_dir,...},{...}]}]},
+ {create_dir,"kernel-2.13.3",
+ [{create_dir,"include",
+ [{copy_file,[...]},{copy_file,...},{...}]}]},
+ {archive,"stdlib-1.16.3.ez",[],
+ [{create_dir,"stdlib-1.16.3",[{...}|...]}]},
+ {create_dir,"stdlib-1.16.3",
+ [{create_dir,"include",[{...}|...]}]}]}]}
+
+
+
+3&gt; TargetDir = "my_target_dir".
+"my_target_dir"
+4&gt; reltool:eval_target_spec(Spec, code:root_dir(), TargetDir).
+{error,"/clearcase/otp/tools/reltool/my_target_dir: no such file or directory"}
+5&gt; file:make_dir("my_target_dir").
+ok
+6&gt; reltool:eval_target_spec(Spec, code:root_dir(), TargetDir).
+ok
+7&gt; file:list_dir(TargetDir).
+{ok,["lib","erts-5.7.3","usr","Install","misc","bin","releases"]}
+8&gt; file:list_dir(filename:join([TargetDir,"lib"])).
+{ok,["stdlib-1.16.3","stdlib-1.16.3.ez","kernel-2.13.3",
+ "kernel-2.13.3.ez","hipe-3.7.3.ez","erts-5.7.3.ez",
+ "crypto-1.6.1","crypto-1.6.1.ez","compiler-4.6.3.ez"]}
+9&gt; file:make_dir("yet_another_target_dir").
+ok
+10&gt; reltool:create_target(Config, "yet_another_target_dir").
+ok
+ </pre>
+
+ </section>
+</chapter>
diff --git a/lib/reltool/doc/src/reltool_intro.xml b/lib/reltool/doc/src/reltool_intro.xml
new file mode 100644
index 0000000000..54bc991da8
--- /dev/null
+++ b/lib/reltool/doc/src/reltool_intro.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2009</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Introduction</title>
+ <prepared>H&aring;kan Mattsson</prepared>
+ <responsible>H&aring;kan Mattsson</responsible>
+ <docno></docno>
+ <approved>H&aring;kan Mattsson</approved>
+ <checked></checked>
+ <date></date>
+ <rev>%VSN%</rev>
+ <file>reltool_intro.xml</file>
+ </header>
+ <p><c>Reltool</c> is a release management tool. It analyses a given
+ Erlang/OTP installation and determines various dependencies between
+ applications. The <c>graphical</c> frontend depicts the dependencies and
+ enables interactive customization of a target system. The backend provides a
+ <c>batch</c> interface for generation of customized target systems.</p>
+
+ <section>
+ <title>Scope and Purpose</title>
+ <p>This manual describes the Reltool application, as a component of the
+ Erlang/Open Telecom Platform development environment. It is assumed that
+ the reader is familiar with the Erlang Development Environment, which is
+ described in a separate User's Guide.</p>
+ </section>
+
+ <section>
+ <title>Prerequisites</title>
+ <p>The following prerequisites is required for understanding the material
+ in the Reltool User's Guide:</p>
+ <list type="bulleted">
+ <item>
+ <p>familiarity with Erlang/OTP system principles and Erlang/OTP design
+ principles</p>
+ </item>
+ </list>
+ <p>The application requires Erlang/OTP release R13B02 or later.</p>
+ </section>
+
+ <section>
+ <title>About This Manual</title>
+ <p>In addition to this introductory chapter, the Reltool User's Guide
+ contains the following chapters:</p>
+ <list type="bulleted">
+ <item>
+ <p>Chapter 2: "Usage" describes the architecture
+ and typical usage of the application.</p>
+ </item>
+ <item>
+ <p>Chapter 3: "Examples" gives some usage examples</p>
+ </item>
+ </list>
+ </section>
+
+ <section>
+ <title>Where to Find More Information</title>
+ <p>Refer to the following documentation for more information about Reltool
+ and about the Erlang/OTP development system:</p>
+ <list type="bulleted">
+ <item>
+ <p>the Reference Manual of <c>Reltool</c></p>
+ </item>
+ <item>
+ <p>the Erlang/OTP <c>System Principles</c></p>
+ </item>
+ <item>
+ <p>the Erlang/OTP <c>Design Principles</c></p>
+ </item>
+ <item>
+ <p>Programming Erlang: Software for a Concurrent World (2007),
+ Pragmatic Bookshelf, ISBN13: 9781934356005.</p>
+ </item>
+ </list>
+ </section>
+</chapter>
diff --git a/lib/reltool/doc/src/reltool_usage.xml b/lib/reltool/doc/src/reltool_usage.xml
new file mode 100644
index 0000000000..885828d1f0
--- /dev/null
+++ b/lib/reltool/doc/src/reltool_usage.xml
@@ -0,0 +1,393 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!DOCTYPE chapter SYSTEM "chapter.dtd">
+
+<chapter>
+ <header>
+ <copyright>
+ <year>2009</year>
+ <year>2009</year>
+ <holder>Ericsson AB, All Rights Reserved</holder>
+ </copyright>
+ <legalnotice>
+ The contents of this file are subject to the Erlang Public License,
+ Version 1.1, (the "License"); you may not use this file except in
+ compliance with the License. You should have received a copy of the
+ Erlang Public License along with this software. If not, it can be
+ retrieved online at http://www.erlang.org/.
+
+ Software distributed under the License is distributed on an "AS IS"
+ basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+ the License for the specific language governing rights and limitations
+ under the License.
+
+ The Initial Developer of the Original Code is Ericsson AB.
+ </legalnotice>
+
+ <title>Usage</title>
+ <prepared>H&aring;kan Mattsson</prepared>
+ <responsible>H&aring;kan Mattsson</responsible>
+ <docno></docno>
+ <approved>H&aring;kan Mattsson</approved>
+ <checked></checked>
+ <date></date>
+ <rev>%VSN%</rev>
+ <file>reltool_usage.xml</file>
+ </header>
+
+ <section>
+ <title>Overview</title>
+ <p>This document focuses on the graphical parts of the tool. The
+ concepts are explained in the reference manual for the module
+ <c>reltool</c>.</p>
+ </section>
+
+ <section>
+ <title>System window</title>
+ <p>The system window is started with the function
+ <c>reltool:start/1</c>. At startup the tool will process the all
+ <c>beam</c> files and <c>app</c> files in order to find out dependencies
+ between applications and their modules. Once all this information has been
+ derived, it will be possible to explore the tool.</p>
+
+ <p>The system window consists of four main pages (tabs):</p>
+
+ <list>
+ <item>Libraries
+ </item>
+ <item>System settings
+ </item>
+ <item>Applications
+ </item>
+ <item>Releases
+ </item>
+ </list>
+
+ <p>Click on a name tag to display its page.</p>
+
+ <section>
+ <title>Libraries</title>
+ <p>On the library page it is possible to control which sources
+ that the tool will use. The page is organized as a tree which
+ can be expanded and collapsed by clicking on the little symbol
+ in the beginning of the expandable/collapsible lines.</p>
+
+ <p>The <c>Root directory</c> can be edited by selecting the
+ line where the path of the root directory is displayed and
+ clicking with the right mouse button. Choose edit in the menu
+ that pops up. </p>
+
+ <p>Library directories can be added, edited or deleted. This
+ is done by selecting the line where the path to a library
+ directory is displayed and clicking with the right mouse
+ button. Choose add, edit or delete in the menu that pops
+ up. New library directories can also be added by selecting the
+ line <c>Library directories</c> and clicking with the right
+ mouse button. Choose add in the menu that pops up.</p>
+
+ <p>Escript files can be added, edited or deleted. This is done
+ by selecting the line where the path to an escript file is
+ displayed and clicking with the right mouse button. Choose
+ add, edit or delete in the menu that pops up. New escripts can
+ also be added by selecting the line <c>Escript files</c> and
+ clicking with the right mouse button. Choose add in the menu
+ that pops up.</p>
+
+ <p>When libraries and escripts are expanded, the names of
+ their contained applications will be displayed. Double click
+ on an application name to launch an application window.</p>
+ </section>
+
+ <section>
+ <title>System settings</title>
+ <p>On the system settings page it is possible to control some
+ global settings that are used as defaults for all
+ applications. Set the <c>Application inclusion policy</c> to
+ <c>include</c> to include all applications that not are
+ explicitly excluded. See <c>incl_cond</c> (application
+ inclusion) and <c>mod_cond</c> (module inclusion) in the
+ reference manual for the module <c>reltool</c> for more
+ info.</p>
+ <p>The system settings page is rather incomplete.</p>
+ </section>
+
+ <section>
+ <title>Applications</title>
+ <p>There are four categories of applications on the
+ applications page. <c>Included</c> contains applications that
+ are explicitly included. <c>Excluded</c> contains applications
+ that are explicitly excluded. <c>Derived</c> contains
+ applications that either are used directly by explicitly
+ included applications or by other derived
+ applications. <c>Available</c> contains the remaining
+ applications.</p>
+
+ <p>Select one or more applications and click on a button
+ directly below the application column to change application
+ category. For example, select an available application and
+ click on its tick button to move the application to the
+ included category. Clicking on the tick symbol for included
+ applications will move the application back to the available
+ category. The tick is undone.</p>
+
+ <p>The symbols in front of the application names are intended
+ to describe the status of the application. There are error
+ symbols and warning symbols that means that there are
+ something that needs attention. The tick symbol means that the
+ application is included or derived and no problem has been
+ detected. The cross symbol means that the application is
+ excluded or available and no problem has been
+ detected. Applications with error symbols are listed first in
+ each category, then comes the warnings and the normal ones
+ (ticks and crosses) are found at the end.</p>
+
+ <p>Double click on an application to launch its application
+ window.</p>
+ </section>
+
+ <section>
+ <title>Releases</title>
+ <p>The releases page is incomplete and very experimental.</p>
+ </section>
+
+ <section>
+ <title>File menu</title>
+
+ <list>
+ <item>
+ <p><c>Display application dependency graph</c> - Launches an
+ application force graph window. All included and derived
+ applications and their dependencies will be shown in a
+ graph.</p>
+ </item>
+ <item>
+ <p><c>Display module dependency graph</c> - Launch a module
+ force graph window. All included and derived modules and
+ their dependencies will be shown in a graph.</p>
+ </item>
+ <item>
+ <p><c>Reset configuration to default</c></p>
+ </item>
+ <item>
+ <p><c>Undo configuration (toggle)</c></p>
+ </item>
+ <item>
+ <p><c>Load configuration</c> - Loads a new configuration from file.</p>
+ </item>
+ <item>
+ <p><c>Save configuration</c> - Saves the current
+ configuration to file. Normally, only the explictit
+ configuration parameters with values that differs from their
+ defaults are saved. But the configuration with or without
+ default values and with or without derived values may also
+ be saved.</p>
+ </item>
+ <item>
+ <p><c>Generate rel, script and boot files</c></p>
+ </item>
+ <item>
+ <p><c>Generate target system</c></p>
+ </item>
+ <item>
+ <p><c>Close</c> - Close the system window and all its subwindows.</p>
+ </item>
+ </list>
+
+ </section>
+
+ <section>
+ <title>Dependencies between applications or modules displayed as a graph</title>
+
+ <p>The dependency graph windows are launched from the file menu
+ in the system window. The graph depicts all included and derived
+ applications/modules and their dependencies.</p>
+
+ <p>It is possible to perform some limited manipulations of the
+ graph. Nodes can be moved, selected, locked or deleted. Move a
+ single node or the entire graph by moving the mouse while the
+ left mouse button is pressed. A node is can be locked into a fix
+ position by holding down the shift button when the left mouse
+ button is released. Select several nodes by moving the mouse
+ while the control key and the left mouse button i
+ pressed. Selected nodes can be locked, unlocked or deleted by
+ klicking on a suitable button.</p>
+
+ <p>The algorithm that is used to draw a graph with as few
+ crossed links as possible is called force graph. A force graph
+ consists of nodes and directed link between nodes. Each node is
+ associated with a repulsive force that pushes nodes away from
+ each other. This force can be adjusted with the left slider or
+ with the mouse wheel. Each link is associated with an attractive
+ force that pulls the nodes nearer each other. This force can be
+ adjusted with the right slider. If this force becomes to strong,
+ the graph will be unstable. The third parameter that can be
+ adjusted is the length of the links. It is adjusted with the
+ middle slider.</p>
+
+ <p>The <c>Freeze</c> button starts/stops the redrawing of the
+ graph. <c>Reset</c> moves the graph to the middle of the window
+ and resets all graph settings to default, with the exception of
+ deleted nodes.</p>
+
+ </section>
+
+ </section>
+
+ <section>
+ <title>Application window</title>
+ <p>The application window is started by double clicking on an
+ application name. The application window consists of four
+ pages (tabs):</p>
+
+ <list>
+ <item>Application settings
+ </item>
+ <item>Modules
+ </item>
+ <item>Application dependencies
+ </item>
+ <item>Module dependencies
+ </item>
+ </list>
+
+ <p>Click on a name tag to display its page.</p>
+
+ <section>
+ <title>Application settings</title>
+ <p>Select version of the application in the <c>Source selection
+ policy</c> part of the page. By default the latest version of the
+ application is selected, but it is possible to override this by
+ explicitly select another version.</p>
+
+ <p>By default the <c>Application inclusion policy</c> on system
+ level is used for all applications. Set the value to
+ <c>include</c> if you want to explicitly include one particular
+ application. Set it to <c>exclude</c> if you want to exclude the
+ application despite that it is used by another (explicitly or
+ implicitly) included application. <c>derived</c> means that the
+ application automatically will be included if some other
+ (explicitly or implicitly) included application uses it.</p>
+
+ <p>By default the <c>Module inclusion policy</c> on system level
+ is used for all applications. Set it to <c>derived</c> if you only
+ want actually used modules to be included. Set it to <c>app</c> if
+ you, besides derived modules, also want the modules listed in the
+ app file to be included. Set it to <c>ebin</c> if you, besides
+ derived modules, also want the modules that exists as beam files
+ on the ebin directory to be included. Set it to <c>all</c> if you
+ want all modules to be included, that is the union of modules
+ found on the ebin directory and listed in the app file.</p>
+
+ <p>The application settings page is rather incomplete.</p>
+ </section>
+
+ <section>
+ <title>Modules</title>
+
+ <p>There are four categories of modules on the modules
+ page. <c>Included</c> contains modules that are explicitly
+ included. <c>Excluded</c> contains modules that are explicitly
+ excluded. <c>Derived</c> contains modules that either are used
+ directly by explicitly included modules or by other derived
+ modules. <c>Available</c> contains the remaining modules.</p>
+
+ <p>Select one or more modules and click on a button directly
+ below the module column to change module category. For
+ example, select an available module and click on its tick
+ button to move the module to the included category. Clicking
+ on the tick symbol for included modules will move the
+ module back to the available category. The tick is
+ undone.</p>
+
+ <p>The symbols in front of the module names are intended to
+ describe the status of the module. There are error symbols
+ and warning symbols that means that there are something that needs
+ attention. The tick symbol means that the module is included
+ or derived and no problem has been detected. The cross symbol
+ means that the module is excluded or available and no problem
+ has been detected. Modules with error symbols are listed
+ first in each category, then comes the warnings and the normal
+ ones (ticks and crosses) are found at the end.</p>
+
+ <p>Double click on an module to launch its module window.</p>
+
+ </section>
+
+ <section>
+ <title>Application dependencies</title>
+ <p>There are four categories of applications on the <c>Application
+ dependencies</c> page. If the application is used by other
+ applications, these are listed under <c>Used by</c>. If the
+ application requires other applications be started before it can
+ be started, these are listed under <c>Required</c>. These
+ applications are listed in the <c>applications</c> part of the
+ <c>app</c> file. If the application includes other applications,
+ these are listed under <c>Included</c>. These applications are
+ listed in the <c>included_applications</c> part of the <c>app</c>
+ file. If the application uses modules other applications, these
+ are listed under <c>Uses</c>.</p>
+
+ <p>Double click on an application name to launch an application window.</p>
+
+ </section>
+
+ <section>
+ <title>Module dependencies</title>
+
+ <p>There are two categories of modules on the <c>Module
+ dependencies</c> page. If the module is used by other modules,
+ these are listed under <c>Modules used by others</c>. If the
+ module uses modules other modules, these are listed under <c>Used
+ modules</c>.</p>
+
+ <p>Double click on an module name to launch a module window.</p>
+
+ </section>
+
+ </section>
+
+ <section>
+ <title>Module window</title>
+
+ <p>The module window is started by double clicking on an module
+ name. The module window consists initially of two pages (tabs):</p>
+
+ <list>
+ <item>Dependencies
+ </item>
+ <item>Code
+ </item>
+ </list>
+
+ <p>Click on a name tag to display its page.</p>
+
+ <section>
+ <title>Dependencies</title>
+
+ <p>There are two categories of modules on the <c>Dependencies</c>
+ page. If the module is used by other modules, these are listed
+ under <c>Modules used by others</c>. If the module uses modules
+ other modules, these are listed under <c>Used modules</c>.</p>
+
+ <p>Double click on an module name to launch a module window.</p>
+
+ </section>
+
+ <section>
+ <title>Code</title>
+
+ <p>On the <c>Code</c> page the Erlang source code is displayed. It
+ is possible to search forwards and backwards for text in the
+ module. Enter a regular expression in the <c>Find</c> field and
+ press enter. It is also possible to goto a certain line on the
+ module. The <c>Back</c> button can be used to go back to the
+ previous position.</p>
+
+ <p>Put the marker on a function name and double click to go to the
+ definition of the function. If the function is defined in another
+ module, that module will be loaded and added to the page list.
+ </p>
+ </section>
+
+ </section>
+
+</chapter>
diff --git a/lib/reltool/doc/src/user_guide.gif b/lib/reltool/doc/src/user_guide.gif
new file mode 100644
index 0000000000..e6275a803d
--- /dev/null
+++ b/lib/reltool/doc/src/user_guide.gif
Binary files differ
diff --git a/lib/reltool/doc/src/warning.gif b/lib/reltool/doc/src/warning.gif
new file mode 100644
index 0000000000..96af52360e
--- /dev/null
+++ b/lib/reltool/doc/src/warning.gif
Binary files differ
diff --git a/lib/reltool/ebin/.gitignore b/lib/reltool/ebin/.gitignore
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/reltool/ebin/.gitignore
diff --git a/lib/reltool/examples/Makefile b/lib/reltool/examples/Makefile
new file mode 100644
index 0000000000..dacfd4e7d4
--- /dev/null
+++ b/lib/reltool/examples/Makefile
@@ -0,0 +1,79 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+#
+
+#
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+include ../vsn.mk
+VSN=$(RELTOOL_VSN)
+
+# ----------------------------------------------------
+# Release Macros
+# ----------------------------------------------------
+RELSYSDIR = $(RELEASE_PATH)/lib/reltool-$(VSN)
+
+# ----------------------------------------------------
+# Common Macros
+# ----------------------------------------------------
+
+
+MODULES =
+
+ERL_FILES= $(MODULES:=.erl)
+
+HRL_FILES =
+
+DATA_FILES = \
+ display_args \
+ mnesia_core_dump_viewer
+
+# TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+TARGET_FILES =
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+ERL_COMPILE_FLAGS += -pa ../ebin
+EBIN = .
+
+# ----------------------------------------------------
+# Make Rules
+# ----------------------------------------------------
+debug opt: $(TARGET_FILES)
+
+clean:
+ rm -f $(TARGET_FILES) *~
+
+docs:
+
+# ----------------------------------------------------
+# Release Targets
+# ----------------------------------------------------
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/examples
+ $(INSTALL_DATA) $(ERL_FILES) $(DATA_FILES) $(HRL_FILES) $(RELSYSDIR)/examples
+
+release_docs_spec:
+
diff --git a/lib/reltool/examples/display_args b/lib/reltool/examples/display_args
new file mode 100644
index 0000000000..bf0994ab7c
--- /dev/null
+++ b/lib/reltool/examples/display_args
@@ -0,0 +1,8 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%! -smp disable
+
+main(Args) ->
+ io:format("Root dir: ~s\n", [code:root_dir()]),
+ io:format("Script args: ~p\n", [Args]),
+ io:format("Smp: ~p\n", [erlang:system_info(smp_support)]).
diff --git a/lib/reltool/examples/mnesia_core_dump_viewer b/lib/reltool/examples/mnesia_core_dump_viewer
new file mode 100755
index 0000000000..725637f95b
--- /dev/null
+++ b/lib/reltool/examples/mnesia_core_dump_viewer
@@ -0,0 +1,54 @@
+#!/usr/bin/env escript
+%% -*- erlang -*-
+%%
+%% Pretty print a Mnesia Core Dump
+%%
+%% core_dump_viewer CoreFile [OutputFile]
+%%
+%% Status codes:
+%%
+%% 0 -> Ok
+%% 1 -> Bad usage
+%% 2 -> Cannot read Mnesia Core file
+%% 3 -> Cannot write to file
+
+-mode(compile).
+
+main([CoreFile | OptOutputFile]) when CoreFile =/= "--help" ->
+ %% io:format("Args: ~p\n", [init:get_arguments()]),
+ case file:read_file(CoreFile) of
+ {ok, Bin} ->
+ Core = binary_to_term(Bin),
+ PrettyCore = io_lib:format("~p\n", [Core]),
+ PrettyBin = list_to_binary(PrettyCore),
+ case OptOutputFile of
+ [OutputFile] ->
+ case file:write_file(OutputFile, PrettyBin) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ io:format("<ERROR> Cannot write to file ~p ~p\n",
+ [OutputFile, Reason]),
+ halt(3)
+ end;
+ [] ->
+ io:format("~p\n", [Core]),
+ io:format("ok\n", []),
+ timer:sleep(timer:seconds(2)), % Poor mans flush of io buffers
+ init:stop();
+ _ ->
+ usage()
+ end;
+ {error, Reason} ->
+ io:format("<ERROR> Cannot read Mnesia Core file ~p ~p\n",
+ [CoreFile, Reason]),
+ halt(2)
+ end;
+main(_BadArgs) ->
+ usage().
+
+usage() ->
+ FullName = escript:script_name(),
+ BaseName = filename:basename(FullName),
+ io:format("usage: ~s CoreFile [OutputFile]\n", [BaseName]),
+ halt(1).
diff --git a/lib/reltool/info b/lib/reltool/info
new file mode 100644
index 0000000000..9aacf0fa6e
--- /dev/null
+++ b/lib/reltool/info
@@ -0,0 +1,7 @@
+group: tools
+short: Reltool is a release management tool. It analyses a given
+short: Erlang/OTP installation and determines various dependencies
+short: between applications. The graphical frontend depicts the
+short: dependencies and enables interactive customization of a
+short: target system. The backend provides a batch interface
+short: for generation of customized target systems.
diff --git a/lib/reltool/src/Makefile b/lib/reltool/src/Makefile
new file mode 100644
index 0000000000..fa24efbb8c
--- /dev/null
+++ b/lib/reltool/src/Makefile
@@ -0,0 +1,99 @@
+#
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+
+include $(ERL_TOP)/make/target.mk
+include $(ERL_TOP)/make/$(TARGET)/otp.mk
+
+# ----------------------------------------------------
+# Application version
+# ----------------------------------------------------
+
+include ../vsn.mk
+
+VSN = $(RELTOOL_VSN)
+APP_VSN = "reltool-$(VSN)"
+
+
+# ----------------------------------------------------
+# Release directory specification
+# ----------------------------------------------------
+
+RELSYSDIR = $(RELEASE_PATH)/lib/reltool-$(VSN)
+
+# ----------------------------------------------------
+# Target Specs
+# ----------------------------------------------------
+
+MODULES = \
+ reltool \
+ reltool_app_win \
+ reltool_fgraph \
+ reltool_fgraph_win \
+ reltool_mod_win \
+ reltool_sys_win \
+ reltool_server \
+ reltool_target \
+ reltool_utils
+
+HRL_FILES =
+
+INTERNAL_HRL_FILES = reltool.hrl reltool_fgraph.hrl
+
+ERL_FILES = $(MODULES:%=%.erl)
+
+TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR))
+
+# ----------------------------------------------------
+# FLAGS
+# ----------------------------------------------------
+
+ERL_COMPILE_FLAGS += +'{parse_transform,sys_pre_attributes}' \
+ +'{attribute,insert,app_vsn,$(APP_VSN)}'
+
+# ----------------------------------------------------
+# Targets
+# ----------------------------------------------------
+
+debug opt: $(TARGET_FILES) $(HRL_FILES)
+
+clean:
+ rm -f $(TARGET_FILES)
+ rm -f core
+
+docs:
+
+# ----------------------------------------------------
+# Dependencies
+# ----------------------------------------------------
+
+$(TARGET_FILES): $(HRL_FILES) $(INTERNAL_HRL_FILES)
+
+# ----------------------------------------------------
+# Release Target
+# ----------------------------------------------------
+
+include $(ERL_TOP)/make/otp_release_targets.mk
+
+release_spec: opt
+ $(INSTALL_DIR) $(RELSYSDIR)/src
+ $(INSTALL_DATA) $(INTERNAL_HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src
+ $(INSTALL_DIR) $(RELSYSDIR)/ebin
+ $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin
+
+release_docs_spec:
+
diff --git a/lib/reltool/src/reltool.app.src b/lib/reltool/src/reltool.app.src
new file mode 100644
index 0000000000..f83042c157
--- /dev/null
+++ b/lib/reltool/src/reltool.app.src
@@ -0,0 +1,37 @@
+%% This is an -*- erlang -*- file.
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+{application, reltool,
+ [{description, "Release management tool"},
+ {vsn, "%VSN%"},
+ {modules, [
+ reltool,
+ reltool_app,
+ reltool_fgraph,
+ reltool_fgraph_win,
+ reltool_gen,
+ reltool_mod,
+ reltool_sys,
+ reltool_server,
+ reltool_utils
+ ]},
+ {applications, [kernel, stdlib]}
+ ]
+}.
diff --git a/lib/reltool/src/reltool.erl b/lib/reltool/src/reltool.erl
new file mode 100644
index 0000000000..e6548bfe68
--- /dev/null
+++ b/lib/reltool/src/reltool.erl
@@ -0,0 +1,253 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(reltool).
+
+%% Public
+-export([
+ main/1, % Escript
+ start/0, start/1, start_link/1, debug/0, % GUI
+ start_server/1, get_server/1, stop/1,
+ get_config/1, get_config/3, get_rel/2, get_script/2,
+ create_target/2, get_target_spec/1, eval_target_spec/3,
+ install/2
+ ]).
+
+-type file() :: string().
+-type dir() :: string().
+-type mod_cond() :: all | app | ebin | derived | none.
+-type incl_cond() :: include | exclude | derived.
+-type debug_info() :: keep | strip.
+-type app_file() :: keep | strip | all.
+-type re_regexp() :: string().
+-type regexps() :: [re_regexp()] | {add, [re_regexp()]} | {del, [re_regexp()]} .
+-type incl_sys_filters() :: regexps().
+-type excl_sys_filters() :: regexps().
+-type incl_app_filters() :: regexps().
+-type excl_app_filters() :: regexps().
+-type incl_archive_filters() :: regexps().
+-type excl_archive_filters() :: regexps().
+-type archive_opt() :: term().
+-type root_dir() :: dir().
+-type lib_dir() :: dir().
+-type profile() :: development | embedded | standalone.
+-type relocatable() :: boolean().
+-type escript_file() :: file().
+-type mod_name() :: atom().
+-type app_name() :: atom().
+-type app_vsn() :: string().
+-type app_type() :: permanent | transient | temporary | load | none.
+-type incl_app() :: app_name().
+-type rel_name() :: string().
+-type rel_vsn() :: string().
+-type boot_rel() :: rel_name().
+-type rel_app() :: app_name()
+ | {app_name(), app_type()}
+ | {app_name(), [incl_app()]}
+ | {app_name(), app_type(), [incl_app()]}.
+-type mod() :: {incl_cond, incl_cond()}
+ | {debug_info, debug_info()}.
+-type app() :: {vsn, app_vsn()}
+ | {mod, mod_name(), mod()}
+ | {mod_cond, mod_cond()}
+ | {incl_cond, incl_cond()}
+ | {app_file, app_file()}
+ | {debug_info, debug_info()}
+ | {incl_app_filters, incl_app_filters()}
+ | {excl_app_filters, excl_app_filters()}
+ | {incl_archive_filters, incl_archive_filters()}
+ | {excl_archive_filters, excl_archive_filters()}.
+-type escript() :: {incl_cond, incl_cond()}.
+-type sys() :: {mod_cond, mod_cond()}
+ | {incl_cond, incl_cond()}
+ | {debug_info, debug_info()}
+ | {app_file, app_file()}
+ | {profile, profile()}
+ | {incl_sys_filters, incl_sys_filters()}
+ | {excl_sys_filters, excl_sys_filters()}
+ | {incl_app_filters, incl_app_filters()}
+ | {excl_app_filters, excl_app_filters()}
+ | {incl_archive_filters, incl_archive_filters()}
+ | {excl_archive_filters, excl_archive_filters()}
+ | {archive_opts, [archive_opt()]}
+ | {root_dir, root_dir()}
+ | {lib_dirs, [lib_dir()]}
+ | {boot_rel, boot_rel()}
+ | {rel, rel_name(), rel_vsn(), [rel_app()]}
+ | {relocatable, relocatable()}
+ | {erts, app()}
+ | {escript, escript_file(), [escript()]}
+ | {app, app_name(), [app()]}.
+-type config() :: {sys, [sys()]}.
+-type option() :: {wx_debug, term()} | {trap_exit, boolean()} | config() | {config, config() | file()}.
+-type options() :: [option()].
+-type server_pid() :: pid().
+-type window_pid() :: pid().
+-type server() :: server_pid() | options().
+-type rel_file() :: term().
+-type script_file() :: term().
+-type reason() :: string().
+-type escript_arg() :: string().
+%%-type base_dir() :: dir().
+%%-type base_file() :: file().
+%%-type top_dir() :: file().
+%%-type top_file() :: file().
+%%-type target_spec() :: [target_spec()]
+%% | {create_dir, base_dir(), [target_spec()]}
+%% | {create_dir, base_dir(), top_dir(), [target_spec()]}
+%% | {archive, base_file(), [archive_opt()], [target_spec()]}
+%% | {copy_file, base_file()}
+%% | {copy_file, base_file(), top_file()}
+%% | {write_file, base_file(), iolist()}
+%% | {strip_beam_file, base_file()}.
+-type target_spec() :: term().
+-type target_dir() :: dir().
+-type incl_defaults() :: boolean().
+-type incl_derived() :: boolean().
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Main function for escript
+-spec main([escript_arg()]) -> ok.
+main(_) ->
+ process_flag(trap_exit, true),
+ {ok, WinPid} = start_link([]),
+ receive
+ {'EXIT', WinPid, shutdown} ->
+ ok;
+ {'EXIT', WinPid, normal} ->
+ ok;
+ {'EXIT', WinPid, Reason} ->
+ io:format("EXIT: ~p\n", [Reason]),
+ erlang:halt(1)
+ end.
+
+%% Start main window process
+-spec start() -> {ok, window_pid()}.
+start() ->
+ start([]).
+
+%% Start main window process
+-spec start(options()) -> {ok, window_pid() | {error, reason()}}.
+start(Options)when is_list(Options) ->
+ {ok, WinPid} = start_link(Options),
+ unlink(WinPid),
+ {ok, WinPid}.
+
+%% Start main window process with wx debugging enabled
+-spec debug() -> {ok, window_pid()}.
+debug() ->
+ {ok, WinPid} = start_link([{wx_debug, 2}]),
+ unlink(WinPid),
+ {ok, WinPid}.
+
+%% Start main window process with options
+-spec start_link(options()) -> {ok, window_pid() | {error, reason()}}.
+start_link(Options) when is_list(Options) ->
+ case reltool_sys_win:start_link(Options) of
+ {ok, WinPid} ->
+ {ok, WinPid};
+ {error, Reason} ->
+ {error, lists:flatten(io_lib:format("~p", [Reason]))}
+ end.
+
+%% Start server process with options
+-spec start_server(options()) -> {ok, server_pid()} | {error, reason()}.
+start_server(Options) ->
+ case reltool_server:start_link(Options) of
+ {ok, ServerPid, _Common, _Sys} ->
+ {ok, ServerPid};
+ {error, Reason} ->
+ {error, lists:flatten(io_lib:format("~p", [Reason]))}
+ end.
+
+%% Start server process with options
+-spec get_server(window_pid()) -> {ok, server_pid()} | {error, reason()}.
+get_server(WinPid) ->
+ case reltool_sys_win:get_server(WinPid) of
+ {ok, ServerPid} ->
+ {ok, ServerPid};
+ {error, Reason} ->
+ {error, lists:flatten(io_lib:format("~p", [Reason]))}
+ end.
+
+%% Stop a server or window process
+-spec stop(server_pid() | window_pid()) -> ok | {error, reason()}.
+stop(Pid) when is_pid(Pid) ->
+ Ref = erlang:monitor(process, Pid),
+ unlink(Pid),
+ exit(Pid, shutdown),
+ receive
+ {'DOWN', Ref, _, _, shutdown} ->
+ ok;
+ {'DOWN', Ref, _, _, Reason} ->
+ {error, lists:flatten(io_lib:format("~p", [Reason]))}
+ end.
+
+%% Internal library function
+-spec eval_server(server(), fun((server_pid()) -> term())) -> {ok, server_pid()} | {error, reason()}.
+eval_server(Pid, Fun) when is_pid(Pid) ->
+ Fun(Pid);
+eval_server(Options, Fun) when is_list(Options), is_function(Fun, 1) ->
+ case start_server(Options) of
+ {ok, Pid} ->
+ Res = Fun(Pid),
+ stop(Pid),
+ Res;
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+%% Get reltool configuration
+-spec get_config(server()) -> {ok, config()} | {error, reason()}.
+get_config(PidOrOption) ->
+ get_config(PidOrOption, false, false).
+
+-spec get_config(server(), incl_defaults(), incl_derived()) -> {ok, config()} | {error, reason()}.
+get_config(PidOrOptions, InclDefaults, InclDerived) when is_pid(PidOrOptions); is_list(PidOrOptions) ->
+ eval_server(PidOrOptions, fun(Pid) -> reltool_server:get_config(Pid, InclDefaults, InclDerived) end).
+
+%% Get contents of release file
+-spec get_rel(server(), rel_name()) -> {ok, rel_file()} | {error, reason()}.
+get_rel(PidOrOptions, RelName) when is_pid(PidOrOptions); is_list(PidOrOptions) ->
+ eval_server(PidOrOptions, fun(Pid) -> reltool_server:get_rel(Pid, RelName) end).
+
+%% Get contents of boot script file
+-spec get_script(server(), rel_name()) -> {ok, script_file()} | {error, reason()}.
+get_script(PidOrOptions, RelName) when is_pid(PidOrOptions); is_list(PidOrOptions) ->
+ eval_server(PidOrOptions, fun(Pid) -> reltool_server:get_script(Pid, RelName) end).
+
+%% Generate a target system
+-spec create_target(server(), target_dir()) -> ok | {error, reason()}.
+create_target(PidOrOptions, TargetDir) when is_pid(PidOrOptions); is_list(PidOrOptions) ->
+ eval_server(PidOrOptions, fun(Pid) -> reltool_server:gen_target(Pid, TargetDir) end).
+
+%% Generate a target system
+-spec get_target_spec(server()) -> {ok, target_spec()} | {error, reason()}.
+get_target_spec(PidOrOptions) when is_pid(PidOrOptions); is_list(PidOrOptions) ->
+ eval_server(PidOrOptions, fun(Pid) -> reltool_server:gen_spec(Pid) end).
+
+%% Generate a target system
+-spec eval_target_spec(target_spec(), root_dir(), target_dir()) -> ok | {error, reason()}.
+eval_target_spec(Spec, SourceDir, TargetDir) when is_list(SourceDir), is_list(TargetDir) ->
+ reltool_target:eval_spec(Spec, SourceDir, TargetDir).
+
+%% Install a target system
+-spec install(rel_name(), dir()) -> ok | {error, reason()}.
+install(RelName, TargetDir) ->
+ reltool_target:install(RelName, TargetDir).
diff --git a/lib/reltool/src/reltool.hrl b/lib/reltool/src/reltool.hrl
new file mode 100644
index 0000000000..736daab0f0
--- /dev/null
+++ b/lib/reltool/src/reltool.hrl
@@ -0,0 +1,202 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-define(APPLICATION, reltool).
+-define(MISSING_APP, '*MISSING*').
+-define(MISSING_APP_TEXT, "*MISSING*").
+
+-record(common,
+ {
+ sys_debug, % term()
+ wx_debug, % term()
+ trap_exit, % bool()
+ app_tab, % ets_tab()
+ mod_tab, % ets_tab()
+ mod_used_by_tab % ets_tab()
+ }).
+
+-record(sys,
+ {
+ %% Sources
+ root_dir, % directory()
+ lib_dirs, % [directory()]
+ escripts, % [file()]
+ mod_cond, % all | app | ebin | derived | none
+ incl_cond, % include | exclude | derived
+ apps, % [#app{}]
+
+ %% Target cond
+ boot_rel, % string()
+ rels, % [#rel{}]
+ emu_name, % string()
+ profile, % standalone | development | embedded
+ incl_sys_filters, % [regexp()]
+ excl_sys_filters, % [regexp()]
+ incl_app_filters, % [regexp()]
+ excl_app_filters, % [regexp()]
+ incl_archive_filters, % [regexp()]
+ excl_archive_filters, % [regexp()]
+ archive_opts, % [zip:create()]
+ relocatable, % bool()
+ app_type, % permanent | transient | temporary | load | none
+ app_file, % keep | strip | all
+ debug_info % keep | strip
+ }).
+
+-record(rel,
+ {
+ name, % string()
+ vsn, % string()
+ rel_apps % [#rel_app{}]
+ }).
+
+-record(rel_app,
+ {
+ name, % atom()
+ app_type, % permanent | transient | temporary | load | none
+ incl_apps % [atom()]
+ }).
+
+-record(app,
+ {%% Static info
+ name, % atom()
+ is_escript, % bool()
+ use_selected_vsn,% bool() | undefined
+ active_dir, % dir_name()
+ sorted_dirs, % [dir_name()]
+ vsn, % string() e.g. "4.7"
+ label, % string() e.g. "mnesia" or "mnesia-4.7"
+ info, % #app_info{} | undefined
+ mods, % [#mod{}]
+
+ %% Static source cond
+ mod_cond, % all | app | ebin | derived | none | undefined
+ incl_cond, % include | exclude | derived | undefined
+
+ %% Static target cond
+ debug_info, % keep | strip | undefined
+ app_file, % keep | strip | all | undefined
+ app_type, % permanent | transient | temporary | load | none
+ incl_app_filters, % [regexp()]
+ excl_app_filters, % [regexp()]
+ incl_archive_filters, % [regexp()]
+ excl_archive_filters, % [regexp()]
+ archive_opts, % [zip_create_opt()]
+
+ %% Dynamic
+ status, % missing | ok
+ uses_mods, % [atom()]
+ used_by_mods, % [atom()]
+ uses_apps, % [atom()]
+ used_by_apps, % [atom()]
+ is_pre_included, % bool()
+ is_included % bool()
+ }).
+
+-record(mod,
+ {%% Static
+ name, % atom()
+ app_name, % atom()
+ incl_cond, % include | exclude | derived | undefined
+ debug_info, % keep | strip | undefined
+ is_app_mod, % bool(),
+ is_ebin_mod, % bool(),
+ uses_mods, % [module()]
+ exists, % bool()
+ %% Dynamic
+ status, % missing | ok
+ used_by_mods, % [atom()]
+ is_pre_included, % bool() | undefined
+ is_included % bool() | undefined
+ }).
+
+%% app - Include all modules in app file
+%% ebin - Include all modules on ebin directory
+%% derived - Include only those modules that others are dependent on
+
+-record(app_info,
+ {
+ description = "",
+ id = "",
+ vsn = "",
+ modules = [],
+ maxP = infinity,
+ maxT = infinity,
+ registered = [],
+ incl_apps = [],
+ applications = [],
+ env = [],
+ mod = undefined,
+ start_phases = undefined
+ }).
+
+-record(regexp, {source, compiled}).
+
+-define(ERR_IMAGE, 0).
+-define(WARN_IMAGE, 1).
+-define(QUEST_IMAGE, 2).
+-define(TICK_IMAGE, 3).
+-define(CROSS_IMAGE, 4).
+-define(SOURCE_IMAGE, 5).
+
+-define(KEYSEARCH(Key, Pos, List),
+ reltool_utils:safe_keysearch(Key, Pos, List, ?MODULE, ?LINE)).
+
+-define(DEFAULT_LIBS, []).
+-define(DEFAULT_APPS, []).
+-define(DEFAULT_INCL_COND, derived).
+-define(DEFAULT_MOD_COND, all).
+-define(DEFAULT_REL_NAME, "start_clean").
+-define(DEFAULT_EMU_NAME, "beam").
+-define(DEFAULT_PROFILE, development).
+-define(DEFAULT_RELOCATABLE, true).
+-define(DEFAULT_APP_TYPE, permanent).
+-define(DEFAULT_APP_FILE, keep).
+-define(DEFAULT_DEBUG_INFO, keep).
+
+-define(DEFAULT_INCL_ARCHIVE_FILTERS, [".*"]).
+-define(DEFAULT_EXCL_ARCHIVE_FILTERS, ["^include$", "^priv$"]).
+-define(DEFAULT_ARCHIVE_OPTS, []).
+
+-define(DEFAULT_INCL_SYS_FILTERS, [".*"]).
+-define(DEFAULT_EXCL_SYS_FILTERS, []).
+-define(DEFAULT_INCL_APP_FILTERS, [".*"]).
+-define(DEFAULT_EXCL_APP_FILTERS, []).
+
+-define(EMBEDDED_INCL_SYS_FILTERS, ["^bin",
+ "^erts",
+ "^lib",
+ "^releases"]).
+-define(EMBEDDED_EXCL_SYS_FILTERS, ["^bin/(erlc|dialyzer|typer)(|\\.exe)$",
+ "^erts.*/bin/(erlc|dialyzer|typer)(|\\.exe)$",
+ "^erts.*/bin/.*(debug|pdb)"]).
+-define(EMBEDDED_INCL_APP_FILTERS, ["^ebin",
+ "^priv",
+ "^include"]).
+-define(EMBEDDED_EXCL_APP_FILTERS, []).
+
+-define(STANDALONE_INCL_SYS_FILTERS, ["^bin/(erl|epmd)(|\\.exe|\\.ini)$",
+ "^bin/start(|_clean).boot$",
+ "^erts.*/bin",
+ "^lib$"]).
+-define(STANDALONE_EXCL_SYS_FILTERS, ["^erts.*/bin/(erlc|dialyzer|typer)(|\\.exe)$",
+ "^erts.*/bin/(start|escript|to_erl|run_erl)(|\\.exe)$",
+ "^erts.*/bin/.*(debug|pdb)"]).
+-define(STANDALONE_INCL_APP_FILTERS, ["^ebin",
+ "^priv"]).
+-define(STANDALONE_EXCL_APP_FILTERS, ["^ebin/.*\\.appup$"]).
diff --git a/lib/reltool/src/reltool_app_win.erl b/lib/reltool/src/reltool_app_win.erl
new file mode 100644
index 0000000000..6083493c02
--- /dev/null
+++ b/lib/reltool/src/reltool_app_win.erl
@@ -0,0 +1,886 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(reltool_app_win).
+
+%% Public
+-export([start_link/4, raise/1, refresh/1, open_mod/2]).
+
+%% Internal
+-export([init/5, loop/1]).
+
+%% sys callback functions
+-export([
+ system_continue/3,
+ system_terminate/4,
+ system_code_change/4
+ ]).
+
+-include_lib("wx/include/wx.hrl").
+-include("reltool.hrl").
+
+-record(state,
+ {parent_pid,
+ xref_pid,
+ mod_wins,
+ sys,
+ common,
+ app,
+ frame,
+ panel,
+ book,
+ status_bar,
+ %% page, % apps | source | config
+ config_app_global, config_app_local, config_app_local_box,
+ config_mod_global, config_mod_local, config_mod_local_box,
+ config_latest, config_selected, config_source_box,
+
+ app_used_by_ctrl, app_required_ctrl, app_incl_ctrl, app_uses_ctrl,
+ mods_source_ctrl, mods_white_ctrl, mods_black_ctrl, mods_derived_ctrl,
+ deps_used_by_ctrl, deps_uses_ctrl,
+ popup_menu}).
+-record(mod_win, {name, pid}).
+
+-define(WIN_WIDTH, 800).
+-define(WIN_HEIGHT, 600).
+%% -define(MODS_MOD_COL_WIDTH, 250).
+%% -define(MODS_APP_COL_WIDTH, 250).
+%% -define(APPS_APP_COL_WIDTH, 250).
+
+-define(CLOSE_ITEM, ?wxID_EXIT). %% Use OS specific version if available
+-define(ABOUT_ITEM, ?wxID_ABOUT). %% Use OS specific
+-define(CONTENTS_ITEM, 300).
+
+-define(MODS_MOD_COL, 0).
+-define(MODS_APP_COL, 1).
+-define(APPS_APP_COL, 0).
+
+-define(source, "Available").
+-define(whitelist, "Included").
+-define(blacklist, "Excluded").
+-define(derived, "Derived").
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Client
+
+start_link(WxEnv, Xref, Common, AppName) ->
+ proc_lib:start_link(?MODULE, init, [self(), WxEnv, Xref, Common, AppName], infinity, []).
+
+raise(Pid) ->
+ reltool_utils:cast(Pid, raise).
+
+refresh(Pid) ->
+ reltool_utils:cast(Pid, refresh).
+
+open_mod(Pid, ModName) ->
+ reltool_utils:call(Pid, {open_mod, ModName}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Server
+
+init(Parent, WxEnv, Xref, C, AppName) ->
+ try
+ do_init(Parent, WxEnv, Xref, C, AppName)
+ catch
+ error:Reason ->
+ exit({Reason, erlang:get_stacktrace()})
+ end.
+
+do_init(Parent, WxEnv, Xref, C, AppName) ->
+ process_flag(trap_exit, C#common.trap_exit),
+ {ok, App} = reltool_server:get_app(Xref, AppName),
+ {ok, Sys} = reltool_server:get_sys(Xref),
+ S = #state{parent_pid = Parent,
+ xref_pid = Xref,
+ mod_wins = [],
+ sys = Sys,
+ common = C,
+ app = App},
+ proc_lib:init_ack(Parent, {ok, self()}),
+ wx:set_env(WxEnv),
+ wx:debug(C#common.wx_debug),
+ S2 = wx:batch(fun() -> create_window(S) end),
+ loop(S2).
+
+loop(#state{xref_pid = Xref, common = C, app = App} = S) ->
+ receive
+ {system, From, Msg} ->
+ Dbg = C#common.sys_debug,
+ sys:handle_system_msg(Msg, From, S#state.parent_pid, ?MODULE, Dbg, S);
+ {cast, _From, raise} ->
+ wxFrame:raise(S#state.frame),
+ wxFrame:setFocus(S#state.frame),
+ ?MODULE:loop(S);
+ {cast, _From, refresh} ->
+ case reltool_server:get_app(Xref, App#app.name) of
+ {ok, App2} ->
+ {ok, Sys} = reltool_server:get_sys(Xref),
+ S2 = redraw_window(S#state{sys = Sys, app = App2}),
+ [ok = reltool_mod_win:refresh(MW#mod_win.pid) || MW <- S2#state.mod_wins],
+ ?MODULE:loop(S2);
+ {error, _Reason} ->
+ wxFrame:destroy(S#state.frame),
+ exit(shutdown)
+ end;
+ {call, ReplyTo, Ref, {open_mod, ModName}} ->
+ S2 = create_mod_window(S, ModName),
+ {value, #mod_win{pid = ModPid}} = lists:keysearch(ModName, #mod_win.name, S2#state.mod_wins),
+ reltool_utils:reply(ReplyTo, Ref, {ok, ModPid}),
+ ?MODULE:loop(S2);
+ #wx{event = #wxSize{}} = Wx ->
+ Wx2 = reltool_utils:get_latest_resize(Wx),
+ S2 = handle_event(S, Wx2),
+ ?MODULE:loop(S2);
+ #wx{obj = ObjRef,
+ event = #wxClose{type = close_window}} ->
+ wxFrame:destroy(ObjRef),
+ exit(shutdown);
+ #wx{} = Wx ->
+ S2 = handle_event(S, Wx),
+ ?MODULE:loop(S2);
+ {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid ->
+ exit(Reason);
+ {'EXIT', Pid, _Reason} = Exit ->
+ exit_warning(Exit),
+ S2 = S#state{mod_wins = lists:keydelete(Pid, #mod_win.pid, S#state.mod_wins)},
+ ?MODULE:loop(S2);
+ Msg ->
+ error_logger:format("~p~p got unexpected message:\n\t~p\n",
+ [?MODULE, self(), Msg]),
+ ?MODULE:loop(S)
+ end.
+
+exit_warning({'EXIT', _Pid, shutdown}) ->
+ ok;
+exit_warning({'EXIT', _Pid, _Reason} = Msg) ->
+ error_logger:format("~p~p got unexpected message:\n\t~p\n",
+ [?MODULE, self(), Msg]).
+
+create_window(#state{app = App} = S) ->
+ Title = app_title(App),
+ Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, []),
+ %% wxFrame:setSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}),
+ Panel = wxPanel:new(Frame, []),
+ StatusBar = wxFrame:createStatusBar(Frame,[]),
+
+ Book = wxNotebook:new(Panel, ?wxID_ANY, []),
+
+ S2 = S#state{frame = Frame,
+ panel = Panel,
+ book = Book,
+ status_bar = StatusBar},
+ Derived = app_to_mods(S2),
+ S3 = create_mods_page(S2, Derived),
+ S4 = create_apps_page(S3, Derived),
+ S5 = create_deps_page(S4, Derived),
+ S6 = create_config_page(S5),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Sizer, Book, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ wxPanel:setSizer(Panel, Sizer),
+ wxSizer:fit(Sizer, Frame),
+ wxSizer:setSizeHints(Sizer, Frame),
+ wxFrame:show(Frame),
+
+ wxFrame:connect(Frame, close_window),
+ S6.
+
+app_title(App) ->
+ lists:concat([?APPLICATION, " - ", App#app.label]).
+
+create_apps_page(S, Derived) ->
+ Panel = wxPanel:new(S#state.book, []),
+ Main = wxBoxSizer:new(?wxVERTICAL),
+ Upper = wxBoxSizer:new(?wxHORIZONTAL),
+ Lower = wxBoxSizer:new(?wxHORIZONTAL),
+
+ UsedByCtrl = create_apps_list_ctrl(Panel, Upper, "Used by"),
+ wxSizer:add(Upper, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+
+ RequiredCtrl = create_apps_list_ctrl(Panel, Upper, "Required"),
+ wxSizer:add(Upper, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ InclCtrl = create_apps_list_ctrl(Panel, Upper, "Included"),
+ wxSizer:add(Upper, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ UsesCtrl = create_apps_list_ctrl(Panel, Upper, "Uses"),
+ S2 = S#state{app_required_ctrl = RequiredCtrl,
+ app_used_by_ctrl = UsedByCtrl,
+ app_incl_ctrl = InclCtrl,
+ app_uses_ctrl = UsesCtrl},
+ redraw_apps(S2, Derived),
+ wxSizer:add(Main, Upper,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+
+ wxSizer:add(Main, Lower,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND}]),
+ wxPanel:setSizer(Panel, Main),
+ wxNotebook:addPage(S2#state.book, Panel, "Application dependencies", []),
+ S2.
+
+create_apps_list_ctrl(Panel, Sizer, Text) ->
+ Width = lists:max([100, ?WIN_WIDTH - 40]) div 4,
+ Height = lists:max([100, ?WIN_HEIGHT - 100]),
+ ListCtrl = wxListCtrl:new(Panel,
+ [{style,
+ ?wxLC_REPORT bor
+ %% ?wxLC_SORT_ASCENDING bor
+ ?wxLC_SINGLE_SEL bor
+ ?wxHSCROLL bor
+ ?wxVSCROLL},
+ {size, {Width, Height}}
+ ]),
+
+ %% Prep images
+ reltool_utils:assign_image_list(ListCtrl),
+
+ %% Prep column label
+ ListItem = wxListItem:new(),
+ wxListItem:setAlign(ListItem, ?wxLIST_FORMAT_LEFT),
+ wxListItem:setText(ListItem, Text),
+ wxListCtrl:insertColumn(ListCtrl, ?APPS_APP_COL, ListItem),
+ %% wxListCtrl:setColumnWidth(ListCtrl, ?APPS_APP_COL, ?APPS_APP_COL_WIDTH),
+ wxListItem:destroy(ListItem),
+
+ wxSizer:add(Sizer, ListCtrl,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+ wxEvtHandler:connect(ListCtrl, size, [{skip, true}, {userData, apps_list_ctrl}]),
+ wxListCtrl:connect(ListCtrl, command_list_item_activated, [{userData, open_app}]),
+ wxWindow:connect(ListCtrl, enter_window),
+ ListCtrl.
+
+create_deps_page(S, Derived) ->
+ Panel = wxPanel:new(S#state.book, []),
+ Main = wxBoxSizer:new(?wxHORIZONTAL),
+
+ UsedByCtrl = create_mods_list_ctrl(Panel, Main, "Modules used by others", " and their applications", undefined, undefined),
+ wxSizer:add(Main, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ UsesCtrl = create_mods_list_ctrl(Panel, Main, "Used modules", " and their applications", undefined, undefined),
+ S2 = S#state{deps_used_by_ctrl = UsedByCtrl,
+ deps_uses_ctrl = UsesCtrl},
+ redraw_mods(S2, Derived),
+ wxPanel:setSizer(Panel, Main),
+ wxNotebook:addPage(S2#state.book, Panel, "Module dependencies", []),
+ S2.
+
+create_mods_page(S, Derived) ->
+ Panel = wxPanel:new(S#state.book, []),
+ MainSz = wxBoxSizer:new(?wxHORIZONTAL),
+
+ SourceCtrl = create_mods_list_ctrl(Panel, MainSz, ?source, "", whitelist_add, blacklist_add),
+ wxSizer:add(MainSz, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ WhiteCtrl = create_mods_list_ctrl(Panel, MainSz, ?whitelist, "", whitelist_del, blacklist_add),
+ wxSizer:add(MainSz, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ BlackCtrl = create_mods_list_ctrl(Panel, MainSz, ?blacklist, "", whitelist_add, blacklist_del),
+ wxSizer:add(MainSz, wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]), [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ DerivedCtrl = create_mods_list_ctrl(Panel, MainSz, ?derived, "", whitelist_add, blacklist_add),
+ S2 = S#state{mods_source_ctrl = SourceCtrl,
+ mods_white_ctrl = WhiteCtrl,
+ mods_black_ctrl = BlackCtrl,
+ mods_derived_ctrl = DerivedCtrl},
+ redraw_mods(S2, Derived),
+ wxPanel:setSizer(Panel, MainSz),
+ wxNotebook:addPage(S2#state.book, Panel, "Modules", []),
+ S2.
+
+create_mods_list_ctrl(Panel, OuterSz, Title, AppText, Tick, Cross) ->
+ ListCtrl = wxListCtrl:new(Panel,
+ [{style,
+ ?wxLC_REPORT bor
+ %% ?wxLC_SORT_ASCENDING bor
+ %% ?wxLC_SINGLE_SEL bor
+ ?wxHSCROLL bor
+ ?wxVSCROLL}]),
+ ToolTip = "Select module(s) or open separate module window with a double click.",
+ wxListCtrl:setToolTip(ListCtrl, ToolTip),
+
+ %% Prep images
+ reltool_utils:assign_image_list(ListCtrl),
+
+ %% Prep column label
+ ListItem = wxListItem:new(),
+ wxListItem:setAlign(ListItem, ?wxLIST_FORMAT_LEFT),
+ wxListItem:setText(ListItem, Title),
+ wxListCtrl:insertColumn(ListCtrl, ?MODS_MOD_COL, ListItem),
+ %% wxListCtrl:setColumnWidth(ListCtrl, ?MODS_MOD_COL, ?MODS_MOD_COL_WIDTH),
+ Prop =
+ case AppText =/= "" of
+ true ->
+ wxListItem:setText(ListItem, AppText),
+ wxListCtrl:insertColumn(ListCtrl, ?MODS_APP_COL, ListItem),
+ %% wxListCtrl:setColumnWidth(ListCtrl, ?MODS_APP_COL, ?MODS_APP_COL_WIDTH),
+ 2;
+ false ->
+ 1
+ end,
+ wxListItem:destroy(ListItem),
+
+ ButtonSz = wxBoxSizer:new(?wxHORIZONTAL),
+ create_button(Panel, ButtonSz, ListCtrl, Title, "wxART_TICK_MARK", Tick),
+ create_button(Panel, ButtonSz, ListCtrl, Title, "wxART_CROSS_MARK", Cross),
+ wxEvtHandler:connect(ListCtrl, size, [{skip, true}, {userData, mods_list_ctrl}]),
+ wxListCtrl:connect(ListCtrl, command_list_item_activated, [{userData, open_mod}]),
+ wxWindow:connect(ListCtrl, enter_window),
+ InnerSz = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(InnerSz, ListCtrl,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+ wxSizer:add(InnerSz, ButtonSz,
+ [{flag, ?wxALL bor ?wxEXPAND}]),
+ wxSizer:add(OuterSz, InnerSz,
+ [{flag, ?wxALL bor ?wxEXPAND},
+ {proportion, Prop}]),
+ ListCtrl.
+
+create_button(_Panel, Sizer, _ListCtrl, _Title, _BitMapName, undefined) ->
+ wxSizer:addStretchSpacer(Sizer);
+create_button(Panel, Sizer, ListCtrl, Title, BitMapName, Action) ->
+ %% InnerSz = wxBoxSizer:new(?wxVERTICAL),
+ BitMap = wxArtProvider:getBitmap(BitMapName),
+ Button = wxBitmapButton:new(Panel, ?wxID_ANY, BitMap, []),
+ ToolTip = action_to_tool_tip(Title, Action),
+ wxBitmapButton:setToolTip(Button, ToolTip),
+ %% wxSizer:add(InnerSz, Button, [{flag, ?wxALIGN_CENTER_HORIZONTAL}]),
+ Opts = [{userData, {mod_button, Action, ListCtrl}}],
+ wxEvtHandler:connect(Button, command_button_clicked, Opts),
+ wxSizer:add(Sizer, Button,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxALIGN_CENTER_HORIZONTAL},
+ {proportion, 1}]).
+
+action_to_tool_tip(Label, Action) ->
+ case Action of
+ whitelist_add when Label =:= ?whitelist ->
+ "Remove selected module(s) from whitelist.";
+ whitelist_add ->
+ "Add selected module(s) to whitelist.";
+ whitelist_del ->
+ "Remove selected module(s)from whitelist.";
+ blacklist_add when Label =:= ?blacklist ->
+ "Remove selected module(s) from blacklist.";
+ blacklist_add ->
+ "Add selected module(s) to blacklist.";
+ blacklist_del ->
+ "Remove selected module(s) from blacklist."
+ end.
+
+create_config_page(#state{app = App} = S) ->
+ Panel = wxPanel:new(S#state.book, []),
+ TopSizer = wxBoxSizer:new(?wxVERTICAL),
+
+ %% Source dirs
+ {LatestRadio, SelectedRadio, SourceBox} =
+ create_double_box(Panel,
+ TopSizer,
+ "Source selection policy",
+ "Use latest version",
+ use_latest_vsn,
+ "Use selected version",
+ use_selected_vsn,
+ "Directories",
+ App#app.sorted_dirs,
+ version),
+
+ InclSizer = wxBoxSizer:new(?wxHORIZONTAL),
+
+ %% Application inclusion
+ {AppGlobalRadio, AppLocalRadio, AppLocalBox} =
+ create_double_box(Panel,
+ InclSizer,
+ "Application inclusion policy",
+ "Use global config",
+ global_incl_cond,
+ "Use application specific config",
+ local_incl_cond,
+ "Application specific",
+ reltool_utils:incl_conds(),
+ incl_cond),
+
+ %% Module inclusion
+ {ModGlobalRadio, ModLocalRadio, ModLocalBox} =
+ create_double_box(Panel,
+ InclSizer,
+ "Module inclusion policy",
+ "Use global config",
+ global_mod_cond,
+ "Use application specific config",
+ local_mod_cond,
+ "Application specific",
+ reltool_utils:mod_conds(),
+ mod_cond),
+ wxSizer:add(TopSizer, InclSizer,
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}, {proportion, 1}]),
+
+ S2 = S#state{config_app_global = AppGlobalRadio,
+ config_app_local = AppLocalRadio,
+ config_app_local_box = AppLocalBox,
+ config_mod_global = ModGlobalRadio,
+ config_mod_local = ModLocalRadio,
+ config_mod_local_box = ModLocalBox,
+ config_latest = LatestRadio,
+ config_selected = SelectedRadio,
+ config_source_box = SourceBox},
+ redraw_config(S2),
+ wxPanel:setSizer(Panel, TopSizer),
+ wxNotebook:addPage(S2#state.book, Panel, "Application settings", []),
+ S2.
+
+create_double_box(Panel, Sizer, TopLabel,
+ OuterText, OuterData,
+ InnerText, InnerData,
+ InternalLabel, InternalChoices, InternalChoiceData) ->
+ TopSizer = wxStaticBoxSizer:new(?wxVERTICAL, Panel,
+ [{label, TopLabel}]),
+ OuterSizer = wxBoxSizer:new(?wxVERTICAL),
+ OuterRadio = wxRadioButton:new(Panel, ?wxID_ANY, OuterText,
+ [{style, ?wxRB_GROUP}]),
+ wxEvtHandler:connect(OuterRadio, command_radiobutton_selected,
+ [{userData, OuterData}]),
+ InnerRadio = wxRadioButton:new(Panel, ?wxID_ANY, InnerText),
+ wxEvtHandler:connect(InnerRadio, command_radiobutton_selected,
+ [{userData, InnerData}]),
+ InnerBox = wxRadioBox:new(Panel,
+ ?wxID_ANY,
+ InternalLabel,
+ ?wxDefaultPosition,
+ ?wxDefaultSize,
+ InternalChoices,
+ []),
+ wxEvtHandler:connect(InnerBox, command_radiobox_selected,
+ [{userData, InternalChoiceData}]),
+ wxSizer:add(OuterSizer, OuterRadio,
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ wxSizer:add(OuterSizer, InnerRadio,
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ wxSizer:add(TopSizer, OuterSizer,
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ wxSizer:add(TopSizer, InnerBox,
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(Sizer, TopSizer,
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}, {proportion, 1}]),
+ {OuterRadio, InnerRadio, InnerBox}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_event(#state{sys = Sys, app = App} = S, Wx) ->
+ %% io:format("wx: ~p\n", [Wx]),
+ case Wx of
+ #wx{obj = ObjRef, event = #wxMouse{type = enter_window}} ->
+ wxWindow:setFocus(ObjRef),
+ S;
+ #wx{obj= ListCtrl, userData = mods_list_ctrl, event = #wxSize{type = size, size = {W, _H}}} ->
+ HasApps = (wxListCtrl:getColumnCount(ListCtrl) > 1),
+ case HasApps of
+ false ->
+ wxListCtrl:setColumnWidth(ListCtrl, ?MODS_MOD_COL, W);
+ true ->
+ wxListCtrl:setColumnWidth(ListCtrl, ?MODS_MOD_COL, (2 * W) div 3),
+ wxListCtrl:setColumnWidth(ListCtrl, ?MODS_APP_COL, W div 3)
+ end,
+ S;
+ #wx{obj= ListCtrl, userData = apps_list_ctrl, event = #wxSize{type = size, size = {W, _H}}} ->
+ wxListCtrl:setColumnWidth(ListCtrl, ?APPS_APP_COL, W),
+ S;
+ #wx{userData = open_app,
+ obj = ListCtrl,
+ event = #wxList{type = command_list_item_activated, itemIndex = Pos}} ->
+ AppBase = wxListCtrl:getItemText(ListCtrl, Pos),
+ {AppName, _AppVsn} = reltool_utils:split_app_name(AppBase),
+ {ok, _AppPid} = reltool_sys_win:open_app(S#state.parent_pid, AppName),
+ S;
+ #wx{userData = open_mod,
+ obj = ListCtrl,
+ event = #wxList{type = command_list_item_activated, itemIndex = Pos}} ->
+ ModName = list_to_atom(wxListCtrl:getItemText(ListCtrl, Pos)),
+ create_mod_window(S, ModName);
+ #wx{userData = global_incl_cond} ->
+ %% Use global setting
+ change_incl_cond(S, App, undefined);
+ #wx{userData = local_incl_cond} ->
+ %% Use app spec setting
+ change_incl_cond(S, App, Sys#sys.incl_cond);
+ #wx{userData = incl_cond,
+ %% Change app spec setting
+ event = #wxCommand{type = command_radiobox_selected,
+ cmdString = Sel}} ->
+ AppCond = reltool_utils:list_to_incl_cond(Sel),
+ change_incl_cond(S, App, AppCond);
+
+ #wx{userData = global_mod_cond} ->
+ %% Use global setting
+ change_mod_cond(S, App, undefined);
+ #wx{userData = local_mod_cond} ->
+ %% Use app spec setting
+ change_mod_cond(S, App, Sys#sys.mod_cond);
+ #wx{userData = mod_cond,
+ %% Change app spec setting
+ event = #wxCommand{type = command_radiobox_selected,
+ cmdString = Sel}} ->
+ ModCond = reltool_utils:list_to_mod_cond(Sel),
+ change_mod_cond(S, App, ModCond);
+
+ #wx{userData = use_latest_vsn} ->
+ %% Use latest version
+ App2 = App#app{use_selected_vsn = undefined},
+ S2 = change_version(S, App2, App#app.active_dir),
+ redraw_window(S2);
+ #wx{userData = use_selected_vsn} ->
+ %% Use selected version
+ App2 = App#app{use_selected_vsn = true},
+ {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
+ S2 = S#state{app = App3},
+ redraw_window(S2);
+ #wx{userData = version,
+ event = #wxCommand{type = command_radiobox_selected,
+ cmdString = ActiveDir}} ->
+ %% Change app source
+ S2 = change_version(S, App, ActiveDir),
+ redraw_window(S2);
+ #wx{userData = {mod_button, Action, ListCtrl},
+ event = #wxCommand{type = command_button_clicked}} ->
+ Items = reltool_utils:get_items(ListCtrl),
+ handle_mod_button(S, Items, Action);
+ _ ->
+ error_logger:format("~p~p got unexpected app event from wx:\n\t~p\n",
+ [?MODULE, self(), Wx]),
+ S
+ end.
+
+create_mod_window(#state{parent_pid = RelPid, xref_pid = Xref, common = C} = S, ModName) ->
+ case lists:keysearch(ModName, #mod_win.name, S#state.mod_wins) of
+ false ->
+ WxEnv = wx:get_env(),
+ {ok, Pid} = reltool_mod_win:start_link(WxEnv, Xref, RelPid, C, ModName),
+ MW = #mod_win{name = ModName, pid = Pid},
+ S#state{mod_wins = [MW | S#state.mod_wins]};
+ {value, MW} ->
+ reltool_app_win:raise(MW#mod_win.pid),
+ S
+ end.
+
+handle_mod_button(#state{app = App} = S, Items, Action) ->
+ App2 = lists:foldl(fun(Item, A) -> move_mod(A, Item, Action) end, App, Items),
+ {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
+ S2 = S#state{app = App3},
+ redraw_window(S2).
+
+move_mod(App, {_ItemNo, ModStr}, Action) ->
+ ModName = list_to_atom(ModStr),
+ Mods = App#app.mods,
+ {value, M} = lists:keysearch(ModName, #mod.name, Mods),
+ AppCond =
+ case Action of
+ whitelist_add ->
+ case M#mod.incl_cond of
+ include -> undefined;
+ exclude -> include;
+ undefined -> include
+ end;
+ whitelist_del ->
+ undefined;
+ blacklist_add ->
+ exclude;
+ blacklist_del ->
+ undefined;
+ _ ->
+ error_logger:format("~p~p got unexpected mod button event: ~p\n\t ~p\n",
+ [?MODULE, self(), ModName, Action]),
+ M#mod.incl_cond
+ end,
+ M2 = M#mod{incl_cond = AppCond},
+ Mods2 = lists:keystore(ModName, #mod.name, Mods, M2),
+ App#app{mods = Mods2}.
+
+change_incl_cond(S, App, NewAppCond) ->
+ App2 = App#app{incl_cond = NewAppCond},
+ {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
+ S2 = S#state{app = App3},
+ redraw_window(S2).
+
+change_mod_cond(S, App, NewModCond) ->
+ App2 = App#app{mod_cond = NewModCond},
+ {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
+ S2 = S#state{app = App3},
+ redraw_window(S2).
+
+change_version(S, App, NewDir) ->
+ App2 = App#app{active_dir = NewDir, label = undefined, vsn = undefined, info = undefined},
+ {ok, App3} = reltool_sys_win:set_app(S#state.parent_pid, App2),
+ Title = app_title(App3),
+ wxFrame:setTitle(S#state.frame, Title),
+ S#state{app = App3}.
+
+redraw_apps(#state{app = #app{info = AppInfo},
+ app_used_by_ctrl = UsedByCtrl,
+ app_required_ctrl = RequiredCtrl,
+ app_incl_ctrl = InclCtrl,
+ app_uses_ctrl = UsesCtrl,
+ xref_pid = Xref},
+ {_SourceMods, _WhiteMods, _BlackMods, _DerivedMods, UsedByMods, UsesMods}) ->
+ UsedByApps = lists:usort([{M#mod.app_name, Image} || {Image, _, M} <- UsedByMods]),
+ Select =
+ fun(AppName) ->
+ {ok, App} = reltool_server:get_app(Xref, AppName),
+ case App#app.status of
+ missing -> {AppName, ?ERR_IMAGE};
+ ok -> {AppName, ?TICK_IMAGE}
+ end
+ end,
+ RequiredApps = lists:sort(lists:map(Select, AppInfo#app_info.applications)),
+ InclApps = lists:map(Select, AppInfo#app_info.incl_apps),
+ UsesApps = lists:usort([{M#mod.app_name, Image} || {Image, _, M} <- UsesMods]),
+ do_redraw_apps(UsedByCtrl, UsedByApps),
+ do_redraw_apps(RequiredCtrl, RequiredApps),
+ do_redraw_apps(InclCtrl, InclApps),
+ do_redraw_apps(UsesCtrl, UsesApps),
+ ok.
+
+do_redraw_apps(ListCtrl, []) ->
+ wxListCtrl:deleteAllItems(ListCtrl);
+ %% wxListCtrl:setColumnWidth(ListCtrl, ?APPS_APP_COL, ?wxLIST_AUTOSIZE_USEHEADER);
+do_redraw_apps(ListCtrl, AppImages) ->
+ wxListCtrl:deleteAllItems(ListCtrl),
+ Add =
+ fun({AppName, ImageId}, {Row, Prev}) when AppName =/= Prev ->
+ wxListCtrl:insertItem(ListCtrl, Row, ""),
+ if (Row rem 2) =:= 0 ->
+ wxListCtrl:setItemBackgroundColour(ListCtrl, Row, {240,240,255});
+ true ->
+ ignore
+ end,
+ Str = atom_to_list(AppName),
+ wxListCtrl:setItem(ListCtrl, Row, ?APPS_APP_COL, Str, [{imageId, ImageId}]),
+ {Row + 1, AppName};
+ ({_, _}, Acc) ->
+ Acc
+ end,
+ wx:foldl(Add, {0, undefined}, AppImages).
+
+%% print(X, X, Format, Args) ->
+%% io:format(Format, Args);
+%% print(_, _, _, _) ->
+%% ok.
+
+redraw_mods(#state{mods_source_ctrl = SourceCtrl,
+ mods_white_ctrl = WhiteCtrl,
+ mods_black_ctrl = BlackCtrl,
+ mods_derived_ctrl = DerivedCtrl,
+ deps_used_by_ctrl = UsedByCtrl,
+ deps_uses_ctrl = UsesCtrl,
+ app = #app{is_pre_included = IsPre, is_included = IsIncl},
+ status_bar = Bar},
+ {SourceMods, WhiteMods, BlackMods, DerivedMods, UsedByMods, UsesMods}) ->
+ InclStatus =
+ case IsIncl of
+ true when IsPre =:= true -> "Whitelist - ";
+ true -> "Derived - ";
+ false -> "Blacklist - ";
+ undefined -> "Source - "
+ end,
+ Status = lists:concat([InclStatus,
+ length(WhiteMods), " whitelisted modules and ",
+ length(DerivedMods), " derived modules."]),
+ wxStatusBar:setStatusText(Bar, Status),
+ opt_redraw_mods(SourceCtrl, SourceMods),
+ opt_redraw_mods(WhiteCtrl, WhiteMods),
+ opt_redraw_mods(BlackCtrl, BlackMods),
+ opt_redraw_mods(DerivedCtrl, DerivedMods),
+ opt_redraw_mods(UsedByCtrl, UsedByMods),
+ opt_redraw_mods(UsesCtrl, UsesMods).
+
+app_to_mods(#state{xref_pid = Xref, app = App}) ->
+ SourceMods = [M || M <- App#app.mods,
+ M#mod.is_included =/= true,
+ M#mod.is_pre_included =/= false],
+ WhiteMods = [M || M <- App#app.mods,
+ M#mod.is_pre_included =:= true],
+ BlackMods = [M || M <- App#app.mods,
+ M#mod.is_pre_included =:= false],
+ DerivedMods = [M || M <- App#app.mods,
+ M#mod.is_included =:= true,
+ M#mod.is_pre_included =/= true],
+ GetMod =
+ fun(ModName) when is_atom(ModName) ->
+ {ok, M} = reltool_server:get_mod(Xref, ModName),
+ if
+ M#mod.app_name =:= App#app.name, M#mod.is_included =:= true ->
+ false;
+ true ->
+ {true, M}
+ end
+ end,
+ UsedByMods = lists:zf(GetMod, App#app.used_by_mods),
+ UsesMods = lists:zf(GetMod, App#app.uses_mods),
+ {
+ [select_image(source, M) || M <- SourceMods],
+ [select_image(whitelist, M) || M <- WhiteMods],
+ [select_image(blacklist, M) || M <- BlackMods],
+ [select_image(derived, M) || M <- DerivedMods],
+ [select_image(used_by, M) || M <- UsedByMods],
+ [select_image(uses, M) || M <- UsesMods]
+ }.
+
+select_image(Kind, M) ->
+ Image =
+ case Kind of
+ blacklist when M#mod.status =:= missing ->
+ ?WARN_IMAGE;
+ source when M#mod.status =:= missing ->
+ ?WARN_IMAGE;
+ _ when M#mod.status =:= missing ->
+ ?ERR_IMAGE;
+ blacklist when M#mod.incl_cond =:= exclude ->
+ ?CROSS_IMAGE;
+ blacklist ->
+ ?SOURCE_IMAGE;
+ source ->
+ ?CROSS_IMAGE;
+ whitelist when M#mod.incl_cond =:= include ->
+ ?TICK_IMAGE;
+ whitelist ->
+ ?SOURCE_IMAGE;
+ derived ->
+ ?TICK_IMAGE;
+ used_by when M#mod.is_included =:= true ->
+ ?TICK_IMAGE;
+ used_by when M#mod.is_included =:= false ->
+ ?WARN_IMAGE;
+ used_by ->
+ ?ERR_IMAGE;
+ uses when M#mod.is_included =:= true ->
+ ?TICK_IMAGE;
+ uses when M#mod.is_included =:= false ->
+ ?WARN_IMAGE;
+ uses ->
+ ?ERR_IMAGE
+ end,
+ {Image, M#mod.app_name, M}.
+
+opt_redraw_mods(undefined, _ImageMods) ->
+ ok;
+opt_redraw_mods(ListCtrl, ImageMods) ->
+ HasApps = (wxListCtrl:getColumnCount(ListCtrl) > 1),
+ do_redraw_mods(ListCtrl, ImageMods, HasApps).
+
+do_redraw_mods(ListCtrl, [], _HasApps) ->
+ wxListCtrl:deleteAllItems(ListCtrl);
+do_redraw_mods(ListCtrl, ImageMods, HasApps) ->
+ wxListCtrl:deleteAllItems(ListCtrl),
+ Add =
+ fun({ImageId, AppName, #mod{name = ModName}}, Row) ->
+ wxListCtrl:insertItem(ListCtrl, Row, ""),
+ if (Row rem 2) =:= 0 ->
+ wxListCtrl:setItemBackgroundColour(ListCtrl, Row, {240,240,255});
+ true ->
+ ignore
+ end,
+ wxListCtrl:setItem(ListCtrl, Row, ?MODS_MOD_COL, atom_to_list(ModName), [{imageId, ImageId}]),
+ case HasApps of
+ false ->
+ ok;
+ true ->
+ wxListCtrl:setItem(ListCtrl,
+ Row,
+ ?MODS_APP_COL,
+ atom_to_list(AppName),
+ [{imageId, ImageId}])
+ end,
+ Row + 1
+ end,
+ wx:foldl(Add, 0, lists:sort(ImageMods)).
+
+redraw_config(#state{sys = #sys{incl_cond = GlobalIncl,
+ mod_cond = GlobalSource},
+ app = #app{incl_cond = LocalIncl,
+ mod_cond = LocalSource,
+ use_selected_vsn = UseSelected,
+ active_dir = ActiveDir,
+ sorted_dirs = SortedDirs},
+ config_app_global = AppGlobalRadio,
+ config_app_local = AppLocalRadio,
+ config_app_local_box = AppLocalBox,
+ config_mod_global = ModGlobalRadio,
+ config_mod_local = ModLocalRadio,
+ config_mod_local_box = ModLocalBox,
+ config_latest = LatestRadio,
+ config_selected = SelectedRadio,
+ config_source_box = SourceBox}) ->
+ redraw_double_box(GlobalIncl,
+ LocalIncl,
+ AppGlobalRadio,
+ AppLocalRadio,
+ AppLocalBox,
+ fun reltool_utils:incl_cond_to_index/1),
+ redraw_double_box(GlobalSource,
+ LocalSource,
+ ModGlobalRadio,
+ ModLocalRadio,
+ ModLocalBox,
+ fun reltool_utils:mod_cond_to_index/1),
+ redraw_double_box(false,
+ UseSelected,
+ LatestRadio,
+ SelectedRadio,
+ SourceBox,
+ fun(true) ->
+ reltool_utils:elem_to_index(ActiveDir, SortedDirs) - 1;
+ (false) ->
+ 0
+ end).
+
+redraw_double_box(Global, Local, GlobalRadio, LocalRadio, LocalBox, GetChoice) ->
+ AppCond =
+ case Local of
+ undefined ->
+ wxRadioButton:setValue(GlobalRadio, true),
+ wxRadioButton:setValue(LocalRadio, false),
+ wxRadioBox:disable(LocalBox),
+ Global;
+ _ ->
+ wxRadioButton:setValue(GlobalRadio, false),
+ wxRadioButton:setValue(LocalRadio, true),
+ wxRadioBox:enable(LocalBox),
+ Local
+ end,
+ Choice = GetChoice(AppCond),
+ wxRadioBox:setSelection(LocalBox, Choice).
+
+redraw_window(S) ->
+ %% wx_misc:beginBusyCursor(),
+ Derived = app_to_mods(S),
+ redraw_config(S),
+ redraw_mods(S, Derived),
+ redraw_apps(S, Derived),
+ %% wx_misc:endBusyCursor(),
+ S.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% sys callbacks
+
+system_continue(_Parent, _Debug, S) ->
+ ?MODULE:loop(S).
+
+system_terminate(Reason, _Parent, _Debug, _S) ->
+ exit(Reason).
+
+system_code_change(S,_Module,_OldVsn,_Extra) ->
+ {ok, S}.
diff --git a/lib/reltool/src/reltool_fgraph.erl b/lib/reltool/src/reltool_fgraph.erl
new file mode 100644
index 0000000000..09c4f8c8ce
--- /dev/null
+++ b/lib/reltool/src/reltool_fgraph.erl
@@ -0,0 +1,163 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(reltool_fgraph).
+
+-export([
+ step/2,
+ step/3
+ ]).
+
+-export([
+ new/0,
+
+ add/3,
+ set/3,
+ del/2,
+
+ is_defined/2,
+ get/2,
+ size/1,
+
+ foreach/2,
+ map/2,
+ foldl/3,
+ mapfoldl/3
+ ]).
+
+-compile(inline).
+-compile({inline_size, 128}).
+
+-include("reltool_fgraph.hrl").
+
+%% KEY-VALUE STORAGE Process dictionary
+new() -> [].
+
+is_defined(Key, _Fg) ->
+ case get(Key) of
+ undefined -> false;
+ _ -> true
+ end.
+
+get(K, _Fg) ->
+ case get(K) of
+ {_, V} -> V;
+ _ -> undefined
+ end.
+
+add(Key, Value, Fg) ->
+ put(Key, {Key, Value}),
+ [Key|Fg].
+
+set(Key, Value, Fg) ->
+ put(Key, {Key, Value}),
+ Fg.
+
+size(Fg) -> length(Fg).
+
+del(Key, Fg) ->
+ erase(Key),
+ lists:delete(Key, Fg).
+
+foreach(Fun, Fg) ->
+ lists:foreach(fun
+ (Key) -> Fun(get(Key))
+ end, Fg),
+ Fg.
+
+map(Fun, Fg) ->
+ lists:foreach(fun
+ (Key) -> put(Key,Fun(get(Key)))
+ end, Fg),
+ Fg.
+
+foldl(Fun, I, Fg) ->
+ lists:foldl(fun
+ (Key, Out) ->
+ Fun(get(Key), Out)
+ end, I, Fg).
+
+mapfoldl(Fun, I, Fg) ->
+ Acc = lists:foldl(fun
+ (Key, Out) ->
+ {Value, Acc} = Fun(get(Key), Out),
+ put(Key, Value),
+ Acc
+ end, I, Fg),
+ {Fg, Acc}.
+
+step(Vs, Es) -> step(Vs, Es, {0,0}).
+step(Vs, Es, Pa) ->
+ ?MODULE:map(fun
+ (Node = {_, #fg_v{ type = static }}) -> Node;
+ ({Key, Value = #fg_v{ p = {Px, Py}, v = {Vx, Vy}, type = dynamic}}) when is_float(Px), is_float(Py), is_float(Vx), is_float(Vy) ->
+ F0 = {0.0,0.0},
+ F1 = coulomb_repulsion(Key, Value, Vs, F0),
+ F2 = hooke_attraction(Key, Value, Vs, Es, F1),
+ F3 = point_attraction(Key, Value, Pa, F2),
+
+ {Fx, Fy} = F3,
+
+ Vx1 = (Vx + ?fg_th*Fx)*?fg_damp,
+ Vy1 = (Vy + ?fg_th*Fy)*?fg_damp,
+
+ Px1 = Px + ?fg_th*Vx1,
+ Py1 = Py + ?fg_th*Vy1,
+
+ {Key, Value#fg_v{ p = {Px1, Py1}, v = {Vx1, Vy1}}};
+ (Node) -> Node
+ end, Vs).
+
+point_attraction(_, #fg_v{ p = P0 }, Pa, {Fx, Fy}) when is_float(Fx), is_float(Fy) ->
+ K = 20,
+ L = 150,
+ {R, {Cx,Cy}} = composition(P0, Pa),
+ F = -K*?fg_stretch*(R - L),
+ {Fx + Cx*F, Fy + Cy*F}.
+
+coulomb_repulsion(K0, #fg_v{ p = P0, q = Q0}, Vs, {Fx0, Fy0}) when is_float(Fx0), is_float(Fy0) ->
+ ?MODULE:foldl(fun
+ ({K1, _}, F) when K1 == K0 -> F;
+ ({_, #fg_v{ p = P1, q = Q1}}, {Fx, Fy}) ->
+ {R, {Cx, Cy}} = composition(P0, P1),
+ F = ?fg_kc*(Q1*Q0)/(R*R+0.0001),
+ {Fx + Cx*F, Fy + Cy*F};
+ (_, F) -> F
+ end, {Fx0, Fy0}, Vs).
+
+hooke_attraction(Key0, #fg_v{ p = P0 }, Vs, Es, {Fx0, Fy0}) when is_float(Fx0), is_float(Fy0) ->
+ ?MODULE:foldl(fun
+ ({{Key1,Key1}, _}, F) -> F;
+ ({{Key1,Key2}, #fg_e{ l = L, k = K}}, {Fx, Fy}) when Key1 =:= Key0->
+ #fg_v{ p = P1} = ?MODULE:get(Key2, Vs),
+ {R, {Cx,Cy}} = composition(P0, P1),
+ F = -K*?fg_stretch*(R - L),
+ {Fx + Cx*F, Fy + Cy*F};
+ ({{Key2,Key1}, #fg_e{ l = L, k = K}}, {Fx, Fy}) when Key1 =:= Key0->
+ #fg_v{ p = P1} = ?MODULE:get(Key2, Vs),
+ {R, {Cx,Cy}} = composition(P0, P1),
+ F = -K*?fg_stretch*(R - L),
+ {Fx + Cx*F, Fy + Cy*F};
+ (_, F) -> F
+ end, {Fx0, Fy0}, Es).
+
+composition({Px1, Py1}, {Px0, Py0}) when is_float(Px1), is_float(Py1), is_float(Px0), is_float(Py0) ->
+ Dx = Px1 - Px0,
+ Dy = Py1 - Py0,
+ R = math:sqrt(Dx*Dx + Dy*Dy + 0.001),
+ {R, {Dx/R, Dy/R}}.
diff --git a/lib/reltool/src/reltool_fgraph.hrl b/lib/reltool/src/reltool_fgraph.hrl
new file mode 100644
index 0000000000..3eba93e3a8
--- /dev/null
+++ b/lib/reltool/src/reltool_fgraph.hrl
@@ -0,0 +1,44 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-define(fg_th, (0.25)).
+-define(fg_damp, (0.75)).
+-define(fg_kc, (1000.0)).
+-define(fg_stretch, (0.005)).
+-define(fg_grav, (9.82)).
+
+%% Ke = 8.854187817e9 [N x M^2 x C^(-2)]
+-define(fg_wind, (0.15)).
+
+-record(fg_e,
+ {
+ l = 10.0,
+ k = 10.0
+ }).
+
+-record(fg_v,
+ {
+ p = {0.0,0.0},
+ v = {0.0,0.0},
+ q = 5.0,
+ m = 1.0,
+ type = dynamic,
+ color = default,
+ resides = undefined,
+ selected = false
+ }).
diff --git a/lib/reltool/src/reltool_fgraph_win.erl b/lib/reltool/src/reltool_fgraph_win.erl
new file mode 100644
index 0000000000..b063fb94ba
--- /dev/null
+++ b/lib/reltool/src/reltool_fgraph_win.erl
@@ -0,0 +1,726 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(reltool_fgraph_win).
+
+-export([
+ new/2,
+ add_node/2,
+ add_node/3,
+ del_node/2,
+ change_node/3,
+ add_link/2,
+ del_link/2,
+ set_dbl_click/2,
+ stop/2
+ ]).
+
+-include_lib("wx/include/wx.hrl").
+-include("reltool_fgraph.hrl").
+
+-record(state,
+ {
+ parent_pid,
+ frame,
+ window,
+ width,
+ height,
+ q_slider,
+ l_slider,
+ k_slider,
+ mouse_act,
+ is_frozen,
+ ticker
+ }).
+
+-record(graph,
+ {
+ pen,
+ brush,
+ font,
+ select = none,
+ offset = {0,0},
+ offset_state = false,
+ ke = 0,
+ vs = [],
+ es = []
+ }).
+
+-define(BRD,10).
+-define(ARC_R, 10).
+
+-define(reset, 80).
+-define(lock, 81).
+-define(unlock, 82).
+-define(move, 83).
+-define(select, 84).
+-define(delete, 85).
+-define(freeze, 86).
+
+-define(q_slider, 90).
+-define(l_slider, 91).
+-define(k_slider, 92).
+
+-define(default_q, 20).
+-define(default_l, 20).
+-define(default_k, 20).
+
+-define(color_bg, {45,50,95}).
+-define(color_fg, {235,245,230}).
+-define(color_default, {10,220,20}).
+-define(color_default_bg, {20,230,30}).
+-define(color_alternate, {220,10,20}).
+-define(color_alternate_bg, {230,20,30}).
+
+add_node(Pid, Key) -> add_node(Pid, Key, default).
+add_node(Pid, Key, Color) -> Pid ! {add_node, Key, Color}.
+del_node(Pid, Key) -> Pid ! {del_node, Key}.
+change_node(Pid, Key, Color) -> Pid ! {change_node, Key, Color}.
+
+add_link(Pid, {FromKey, ToKey}) -> Pid ! {add_link, {FromKey, ToKey}}.
+del_link(Pid, {FromKey, ToKey}) -> Pid ! {del_link, {FromKey, ToKey}}.
+
+stop(Pid, Reason) ->
+ Ref = erlang:monitor(process, Pid),
+ Pid ! {stop, Reason},
+ receive
+ {'DOWN', Ref, _, _, _} ->
+ ok
+ end.
+
+set_dbl_click(Pid, Fun) -> Pid ! {set_dbl_click, Fun}.
+
+new(Parent, Options) ->
+ Env = wx:get_env(),
+ Me = self(),
+ Pid = spawn_link(fun() -> init([Parent, Me, Env, Options]) end),
+ receive {Pid, {?MODULE, Panel}} -> {Pid,Panel} end.
+
+init([ParentWin, Pid, Env, Options]) ->
+ wx:set_env(Env),
+
+ BReset = wxButton:new(ParentWin, ?reset, [{label,"Reset"}]),
+ BFreeze = wxButton:new(ParentWin, ?freeze, [{label,"Freeze"}]),
+ BLock = wxButton:new(ParentWin, ?lock, [{label,"Lock"}]),
+ BUnlock = wxButton:new(ParentWin, ?unlock, [{label,"Unlock"}]),
+ BDelete = wxButton:new(ParentWin, ?delete, [{label,"Delete"}]),
+
+ SQ = wxSlider:new(ParentWin, ?q_slider, ?default_q, 1, 500, [{style, ?wxVERTICAL}]),
+ SL = wxSlider:new(ParentWin, ?l_slider, ?default_l, 1, 500, [{style, ?wxVERTICAL}]),
+ SK = wxSlider:new(ParentWin, ?k_slider, ?default_k, 1, 500, [{style, ?wxVERTICAL}]),
+ Win = wxWindow:new(ParentWin, ?wxID_ANY, Options),
+
+ ButtonSizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(ButtonSizer, BReset),
+ wxSizer:add(ButtonSizer, BFreeze),
+ wxSizer:add(ButtonSizer, BLock),
+ wxSizer:add(ButtonSizer, BUnlock),
+ wxSizer:add(ButtonSizer, BDelete),
+
+ SliderSizer = wxBoxSizer:new(?wxHORIZONTAL),
+ wxSizer:add(SliderSizer, SQ, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(SliderSizer, SL, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(SliderSizer, SK, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(ButtonSizer, SliderSizer, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ WindowSizer = wxBoxSizer:new(?wxHORIZONTAL),
+ wxSizer:add(WindowSizer, ButtonSizer, [{flag, ?wxEXPAND}, {proportion, 0}]),
+ wxSizer:add(WindowSizer, Win, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ wxButton:setToolTip(BReset, "Remove selection and unlock all nodes."),
+ wxButton:setToolTip(BFreeze, "Start/stop redraw of screen."),
+ wxButton:setToolTip(BLock, "Lock all selected nodes."),
+ wxButton:setToolTip(BUnlock, "Unlock all selected nodes."),
+ wxButton:setToolTip(BDelete, "Delete all selected nodes."),
+
+ wxButton:setToolTip(SQ, "Control repulsive force. This can also be controlled with the mouse wheel on the canvas."),
+ wxButton:setToolTip(SL, "Control link length."),
+ wxButton:setToolTip(SK, "Control attractive force. Use with care."),
+ wxButton:setToolTip(Win,
+ "Drag mouse while left mouse button is pressed to perform various operations. "
+ "Combine with control key to select. Combine with shift key to lock single node."),
+
+ wxButton:connect(BReset, command_button_clicked),
+ wxButton:connect(BFreeze, command_button_clicked),
+ wxButton:connect(BLock, command_button_clicked),
+ wxButton:connect(BUnlock, command_button_clicked),
+ wxButton:connect(BDelete, command_button_clicked),
+
+ wxWindow:connect(SQ, command_slider_updated),
+ wxWindow:connect(SL, command_slider_updated),
+ wxWindow:connect(SK, command_slider_updated),
+
+ wxWindow:connect(Win, enter_window),
+ wxWindow:connect(Win, move),
+ wxWindow:connect(Win, motion),
+ wxWindow:connect(Win, mousewheel),
+ wxWindow:connect(Win, key_up),
+ wxWindow:connect(Win, left_down),
+ wxWindow:connect(Win, left_up),
+ wxWindow:connect(Win, right_down),
+ wxWindow:connect(Win, paint, [{skip, true}]),
+
+ Pen = wxPen:new({0,0,0}, [{width, 3}]),
+ Font = wxFont:new(12, ?wxSWISS, ?wxNORMAL, ?wxNORMAL,[]),
+ Brush = wxBrush:new({0,0,0}),
+
+ Pid ! {self(), {?MODULE, WindowSizer}},
+
+ wxWindow:setFocus(Win), %% Get keyboard focus
+
+ Vs = reltool_fgraph:new(),
+ Es = reltool_fgraph:new(),
+
+ Me = self(),
+ Ticker = spawn_link(fun() -> ticker_init(Me) end),
+
+ loop( #state{ parent_pid = Pid,
+ q_slider = SQ,
+ l_slider = SL,
+ k_slider = SK,
+ mouse_act = ?move,
+ frame = ParentWin,
+ window = Win,
+ is_frozen = false,
+ ticker = Ticker},
+ #graph{ vs = Vs,
+ es = Es,
+ pen = Pen,
+ font = Font,
+ brush = Brush}).
+
+graph_add_node_unsure(Key, State, G = #graph{ vs = Vs }) ->
+ case reltool_fgraph:is_defined(Key, Vs) of
+ true -> G;
+ false -> graph_add_node(Key, State, G)
+ end.
+
+graph_add_node(Key, Color, G = #graph{ vs = Vs}) ->
+ Q = 20.0, % repulsive force
+ M = 0.5, % mass
+ P = {float(450 + random:uniform(100)),
+ float(450 + random:uniform(100))},
+ G#graph{ vs = reltool_fgraph:add(Key, #fg_v{ p = P, m = M, q = Q, color = Color}, Vs)}.
+
+graph_change_node(Key, Color, G) ->
+ case reltool_fgraph:get(Key, G#graph.vs) of
+ undefined ->
+ G;
+ V ->
+ G#graph{ vs = reltool_fgraph:set(Key, V#fg_v{ color = Color }, G#graph.vs)}
+ end.
+
+graph_del_node(Key, G = #graph{ vs = Vs0, es = Es0}) ->
+ Vs = reltool_fgraph:del(Key, Vs0),
+ Es = delete_edges(Es0, [Key]),
+ G#graph{ vs = Vs, es = Es }.
+
+graph_add_link(Key0, Key1, G = #graph{ es = Es}) ->
+ K = 60.0, % attractive force
+ L = 5.0, % spring length
+ G#graph{ es = reltool_fgraph:add({Key0, Key1}, #fg_e{ k = K, l = L}, Es) }.
+
+graph_del_link(Key0, Key1, G = #graph{ es = Es}) ->
+ G#graph{ es = reltool_fgraph:del({Key0, Key1}, Es) }.
+
+ticker_init(Pid) ->
+ ticker_loop(Pid, 50).
+ticker_loop(Pid, Time) ->
+ receive after Time ->
+ Pid ! {self(), redraw},
+ T0 = now(),
+ receive {Pid, ok} -> ok end,
+ T1 = now(),
+ D = timer:now_diff(T1, T0)/1000,
+ case round(40 - D) of
+ Ms when Ms < 0 ->
+ %io:format("ticker: wait is 0 ms [fg ~7s ms] [fps ~7s]~n", [s(D), s(1000/D)]),
+ ticker_loop(Pid, 0);
+ Ms ->
+ %io:format("ticker: wait is ~3s ms [fg ~7s ms] [fps ~7s]~n", [s(Ms), s(D), s(1000/40)]),
+ ticker_loop(Pid, Ms)
+ end
+ end.
+
+delete_edges(Es, []) ->
+ Es;
+delete_edges(Es, [Key|Keys]) ->
+ Edges = reltool_fgraph:foldl(fun
+ ({{K1, K2}, _}, Out) when K1 =:= Key -> [{K1,K2}|Out];
+ ({{K1, K2}, _}, Out) when K2 =:= Key -> [{K1,K2}|Out];
+ (_, Out) -> Out
+ end, [], Es),
+ Es1 = lists:foldl(fun
+ (K, Esi) -> reltool_fgraph:del(K, Esi)
+ end, Es, Edges),
+ delete_edges(Es1, Keys).
+
+
+set_charge(Q, Vs) -> % Repulsive force
+ F = fun({Key, Value}) -> {Key, Value#fg_v{ q = Q}} end,
+ reltool_fgraph:map(F, Vs).
+
+set_length(L, Es) -> % Spring length
+ F = fun({Ps, E}) -> {Ps, E#fg_e{ l = L}} end,
+ reltool_fgraph:map(F, Es).
+
+set_spring(K, Es) -> % Attractive force
+ F = fun({Ps, E}) -> {Ps, E#fg_e{ k = K}} end,
+ reltool_fgraph:map(F, Es).
+
+loop(S, G) ->
+ receive
+ #wx{id = ?reset, event = #wxCommand{type=command_button_clicked}} ->
+ %% Remove selection and unlock all nodes
+ Q = ?default_q,
+ L = ?default_l,
+ K = ?default_k,
+ wxSlider:setValue(S#state.q_slider, Q),
+ wxSlider:setValue(S#state.l_slider, L),
+ wxSlider:setValue(S#state.k_slider, K),
+ Es = set_length(L, G#graph.es),
+ Es2 = set_spring(K, Es),
+
+ Vs2 = reltool_fgraph:map(fun({Key, V}) ->
+ {Key, V#fg_v{selected = false, type = dynamic, q = Q}}
+ end,
+ G#graph.vs),
+
+ {Xs, Ys} = reltool_fgraph:foldl(fun({_Key, #fg_v{p = {X, Y}}}, {Xs, Ys}) ->
+ {[X| Xs], [Y | Ys]}
+ end,
+ {[], []},
+ Vs2),
+ %% io:format("Before: ~p\n", [G#graph.offset]),
+ Offset =
+ case length(Xs) of
+ 0 ->
+ {0, 0};
+ N ->
+ MeanX = (lists:sum(Xs) / N),
+ MeanY = (lists:sum(Ys) / N),
+ {SizeX, SizeY} = wxWindow:getSize(S#state.window),
+ %% io:format("Min: ~p\n", [{lists:min(Xs), lists:min(Ys)}]),
+ %% io:format("Mean: ~p\n", [{MeanX, MeanY}]),
+ %% io:format("Max: ~p\n", [{lists:max(Xs), lists:max(Ys)}]),
+ %% io:format("Size: ~p\n", [{SizeX, SizeY}]),
+ %% {XM - (XS / 2), YM - (YS / 2)}
+ %% {0 - lists:min(Xs) + 20, 0 - lists:min(Ys) + 20}
+ {0 - MeanX + (SizeX / 2), 0 - MeanY + (SizeY / 2)}
+ end,
+ %% io:format("After: ~p\n", [Offset]),
+ loop(S, G#graph{vs = Vs2, es = Es2, offset = Offset, offset_state = false});
+ #wx{id = ?freeze, event = #wxCommand{type=command_button_clicked}} ->
+ %% Start/stop redraw of screen
+ IsFrozen =
+ case S#state.is_frozen of
+ true ->
+ S#state.ticker ! {self(), ok},
+ false;
+ false ->
+ true
+ end,
+ loop(S#state{is_frozen = IsFrozen}, G);
+ #wx{id = ?lock, event = #wxCommand{type=command_button_clicked}} ->
+ %% Lock all selected nodes
+ Vs = reltool_fgraph:map(fun
+ ({Key, V = #fg_v{selected = true}}) ->
+ {Key, V#fg_v{ type = static }};
+ (KV) -> KV
+ end, G#graph.vs),
+ loop(S, G#graph{ vs = Vs });
+ #wx{id = ?unlock, event = #wxCommand{type=command_button_clicked}} ->
+ %% Unlock all selected nodes
+ Vs = reltool_fgraph:map(fun
+ ({Key, V = #fg_v{selected = true}}) ->
+ {Key, V#fg_v{ type = dynamic }};
+ (KV) -> KV
+ end, G#graph.vs),
+ loop(S, G#graph{ vs = Vs });
+ #wx{id = ?delete, event = #wxCommand{type=command_button_clicked}} ->
+ %% Delete all selected nodes
+ {Vs1, Keys} = reltool_fgraph:foldl(fun
+ ({Key, #fg_v{ selected = true}}, {Vs, Ks}) ->
+ {reltool_fgraph:del(Key,Vs), [Key|Ks]};
+ (_, {Vs, Ks}) -> {Vs, Ks}
+ end, {G#graph.vs,[]}, G#graph.vs),
+ Es = delete_edges(G#graph.es, Keys),
+ loop(S, G#graph{ vs = Vs1, es = Es});
+
+ #wx{id = ?select, event = #wxCommand{type=command_button_clicked}} ->
+ loop(S#state{ mouse_act = ?select }, G);
+
+ #wx{id = ?move, event = #wxCommand{type=command_button_clicked}} ->
+ loop(S#state{ mouse_act = ?move }, G);
+
+ #wx{id = ?q_slider, event = #wxCommand{type=command_slider_updated, commandInt = Q}} ->
+ loop(S, G#graph{ vs = set_charge(Q, G#graph.vs)});
+ #wx{id = ?l_slider, event = #wxCommand{type=command_slider_updated, commandInt = L}} ->
+ loop(S, G#graph{ es = set_length(L, G#graph.es)});
+ #wx{id = ?k_slider, event = #wxCommand{type=command_slider_updated, commandInt = K}} ->
+ loop(S, G#graph{ es = set_spring(K, G#graph.es)});
+ #wx{event=#wxKey{type=key_up, keyCode = 127}} -> % delete
+ {Vs1, Keys} =
+ reltool_fgraph:foldl(fun({Key, #fg_v{ selected = true}}, {Vs, Ks}) ->
+ {reltool_fgraph:del(Key,Vs), [Key|Ks]};
+ (_, {Vs, Ks}) ->
+ {Vs, Ks}
+ end,
+ {G#graph.vs,[]}, G#graph.vs),
+ Es = delete_edges(G#graph.es, Keys),
+ loop(S, G#graph{ vs = Vs1, es = Es});
+ #wx{event=#wxKey{type=key_up}} ->
+ loop(S, G);
+ #wx{event=#wxKey{type=key_down}} ->
+ loop(S, G);
+
+ %% mouse
+ #wx{event=#wxMouse{type=left_down, shiftDown=Shift, controlDown=Ctrl, x=X, y=Y}} ->
+ if
+ Shift ->
+ loop(S, mouse_left_down_move(G, {X,Y}));
+ Ctrl ->
+ loop(S, mouse_left_down_select(G, {X,Y}));
+ S#state.mouse_act =:= ?move ->
+ loop(S, mouse_left_down_move(G, {X,Y}));
+ S#state.mouse_act =:= ?select ->
+ loop(S, mouse_left_down_select(G, {X,Y}))
+ end;
+ #wx{event=#wxMouse{type=motion, shiftDown=Shift, controlDown=Ctrl, x=X, y=Y}} ->
+ if
+ Shift ->
+ loop(S, mouse_motion_move(G, {X,Y}));
+ Ctrl ->
+ loop(S, mouse_motion_select(G, {X,Y}));
+ S#state.mouse_act =:= ?move ->
+ loop(S, mouse_motion_move(G, {X,Y}));
+ S#state.mouse_act =:= ?select ->
+ loop(S, mouse_motion_select(G, {X,Y}))
+ end;
+ #wx{event=#wxMouse{type=left_up, shiftDown=Shift, controlDown=Ctrl, x=X, y=Y}} ->
+ if
+ Shift ->
+ loop(S, mouse_left_up_move(G, {X,Y}, Shift));
+ Ctrl ->
+ loop(S, mouse_left_up_select(G, {X,Y}));
+ S#state.mouse_act =:= ?move ->
+ loop(S, mouse_left_up_move(G, {X,Y}, Shift));
+ S#state.mouse_act =:= ?select ->
+ loop(S, mouse_left_up_select(G, {X,Y}))
+ end;
+
+ #wx{event=#wxMouse{type=right_down,x=_X,y=_Y}} ->
+ loop(S, G);
+ %% mouse wheel
+ #wx{event=#wxMouse{type=mousewheel, wheelRotation=Rotation}} ->
+ Q = wxSlider:getValue(S#state.q_slider),
+ if
+ Rotation > 0, Q > 5 ->
+ wxSlider:setValue(S#state.q_slider, Q - 4),
+ loop(S, G#graph{ vs = set_charge(Q - 4, G#graph.vs) });
+ Rotation < 0 ->
+ wxSlider:setValue(S#state.q_slider, Q + 4),
+ loop(S, G#graph{ vs = set_charge(Q + 4, G#graph.vs) });
+ true ->
+ loop(S, G)
+ end;
+
+ %% #wx{event=#wxClose{}} ->
+ %% catch wxWindow:'Destroy'(S#state.frame);
+ %% #wx{id=?wxID_EXIT, event=#wxCommand{type=command_menu_selected}} ->
+ %% wxWindow:close(S#state.frame,[]);
+ #wx{obj=_Win,event=#wxPaint{}} ->
+ redraw(S, G),
+ loop(S, G);
+ #wx{obj=Win,event=#wxMouse{type=enter_window}} ->
+ wxWindow:setFocus(Win),
+ loop(S, G);
+
+ %% Graph manipulation
+ {add_node, Key, State} ->
+ loop(S, graph_add_node_unsure(Key, State, G));
+ {del_node, Key} ->
+ loop(S, graph_del_node(Key, G));
+ {change_node, Key, Color} ->
+ loop(S, graph_change_node(Key, Color, G));
+ {add_link, {K0,K1}} ->
+ loop(S, graph_add_link(K0, K1, G));
+ {del_link, {K0,K1}} ->
+ loop(S, graph_del_link(K0, K1, G));
+
+ {Req, redraw} ->
+ {SizeX, SizeY} = wxWindow:getSize(S#state.window),
+ Vs = reltool_fgraph:step(G#graph.vs, G#graph.es, {SizeX/2.0 - 20.0, SizeY/2.0}),
+ case S#state.is_frozen of
+ false ->
+ Req ! {self(), ok};
+ true ->
+ ignore
+ end,
+ redraw(S, G),
+ loop(S, G#graph{ vs = Vs} );
+
+ {stop, Reason} ->
+ unlink(S#state.parent_pid),
+ exit(Reason);
+
+ Other ->
+ error_logger:format("~p~p got unexpected message:\n\t~p\n",
+ [?MODULE, self(), Other]),
+ loop(S, G)
+ end.
+
+mouse_left_down_select(G, {X0,Y0}) ->
+ G#graph{ select = {{X0,Y0}, {X0,Y0}} }.
+
+mouse_left_down_move(#graph{vs = Vs} = G, {X, Y}) ->
+ % point on node?
+ case coord_to_key(G, {X, Y}) of
+ false ->
+ G#graph{ offset_state = {X,Y}};
+ {true, Key} ->
+ V = #fg_v{ type = Type} = reltool_fgraph:get(Key, Vs),
+ G#graph{ vs = reltool_fgraph:set(Key, V#fg_v{ type = moving}, Vs), select = {node, Key, Type, X, Y} }
+ end.
+
+coord_to_key(#graph{vs = Vs, offset = {Xo, Yo}}, {X, Y}) ->
+ Xr = X - Xo,
+ Yr = Y - Yo,
+ reltool_fgraph:foldl(fun({Key, #fg_v{ p = {Px, Py}}}, _) when abs(Px - Xr) < 10,
+ abs(Py - Yr) < 10 -> {true, Key};
+ (_, Out) -> Out
+ end, false, Vs).
+
+mouse_left_up_select(G, {_X,_Y}) ->
+ case G#graph.select of
+ {{X0,Y0}, {X1, Y1}} ->
+ {Xo, Yo} = G#graph.offset,
+ Xmin = lists:min([X0,X1]) - Xo,
+ Xmax = lists:max([X1,X0]) - Xo,
+ Ymin = lists:min([Y0,Y1]) - Yo,
+ Ymax = lists:max([Y1,Y0]) - Yo,
+ Vs = reltool_fgraph:map(fun
+ ({Key, Value = #fg_v{ p = {Px, Py}}})
+ when Px > Xmin, Px < Xmax, Py > Ymin, Py < Ymax ->
+ {Key, Value#fg_v{ selected = true }};
+ ({Key, Value}) -> {Key, Value#fg_v{ selected = false }}
+ end, G#graph.vs),
+ G#graph{ select = none, vs = Vs};
+ _ ->
+ G#graph{ select = none}
+ end.
+
+mouse_left_up_move(G = #graph{ select = Select, vs = Vs} = G, {X,Y}, Shift) ->
+ case Select of
+ {node, Key, _, X, Y} ->
+ io:format("click: ~p\n", [Key]),
+ G#graph{ select = none, offset_state = false };
+ {node, Key, Type, _, _} ->
+ V = reltool_fgraph:get(Key, Vs),
+ Type2 =
+ case Shift of
+ true -> static;
+ false -> Type
+ end,
+ G#graph{ select = none,
+ vs = reltool_fgraph:set(Key, V#fg_v{ type = Type2}, Vs),
+ offset_state = false };
+ _ ->
+ G#graph{ select = none, offset_state = false }
+ end.
+
+mouse_motion_select(G, {X,Y}) ->
+ case G#graph.select of
+ {P0, _P1} -> G#graph{ select = {P0, {X,Y}}};
+ _ -> G
+ end.
+
+mouse_motion_move(G = #graph{ select = {node, Key, _, _, _}, vs = Vs}, {X,Y}) ->
+ {Xo, Yo} = G#graph.offset,
+ V = reltool_fgraph:get(Key, Vs),
+ V2 = V#fg_v{ p = {float(X - Xo), float(Y - Yo)}},
+ G#graph{ vs = reltool_fgraph:set(Key, V2, Vs) };
+mouse_motion_move(G, {X,Y}) ->
+ case G#graph.offset_state of
+ {X1,Y1} ->
+ {X0, Y0} = G#graph.offset,
+ G#graph{ offset_state = {X,Y},
+ offset = {X0 - (X1 - X), Y0 - (Y1 - Y)} };
+ _ ->
+ G
+ end.
+
+redraw(#state{window=Win}, G) ->
+ DC0 = wxClientDC:new(Win),
+ DC = wxBufferedDC:new(DC0),
+ Size = wxWindow:getSize(Win),
+ redraw(DC, Size, G),
+ wxBufferedDC:destroy(DC),
+ wxClientDC:destroy(DC0),
+ ok.
+
+redraw(DC, _Size, G) ->
+ wx:batch(fun() ->
+
+ Pen = G#graph.pen,
+ Font = G#graph.font,
+ Brush = G#graph.brush,
+ wxDC:setTextForeground(DC,?color_fg),
+ wxBrush:setColour(Brush, ?color_bg),
+ wxDC:setBrush(DC, Brush),
+ wxDC:setBackground(DC, Brush),
+ wxPen:setWidth(Pen, 1),
+ wxDC:clear(DC),
+
+ % draw vertices and edges
+ wxPen:setColour(Pen, ?color_fg),
+ wxDC:setPen(DC,Pen),
+
+ %draw_es(DC, G#graph.es_pts, G#graph.offset),
+ draw_es(DC, G#graph.vs, G#graph.es, G#graph.offset, Pen, Brush),
+ draw_vs(DC, G#graph.vs, G#graph.offset, Pen, Brush),
+
+ % draw selection box
+ wxPen:setColour(Pen, ?color_fg),
+ wxDC:setPen(DC,Pen),
+ draw_select_box(DC, G#graph.select),
+
+ % draw information text
+ wxFont:setWeight(Font,?wxNORMAL),
+ draw_text(DC, reltool_fgraph:size(G#graph.vs), reltool_fgraph:size(G#graph.es), G#graph.ke),
+ ok
+ end).
+
+draw_select_box(DC, {{X0,Y0}, {X1,Y1}}) ->
+ draw_line(DC, {X0,Y0}, {X1,Y0}, {0,0}),
+ draw_line(DC, {X1,Y1}, {X1,Y0}, {0,0}),
+ draw_line(DC, {X1,Y1}, {X0,Y1}, {0,0}),
+ draw_line(DC, {X0,Y0}, {X0,Y1}, {0,0}),
+ ok;
+draw_select_box(_DC, _) ->
+ ok.
+
+draw_es(DC, Vs, Es, Po, Pen, Brush) ->
+ reltool_fgraph:foreach(fun
+ ({{K1, K2}, _}) ->
+ #fg_v{ p = P1} = reltool_fgraph:get(K1, Vs),
+ #fg_v{ p = P2} = reltool_fgraph:get(K2, Vs),
+ draw_arrow(DC, P1, P2, Po, Pen, Brush)
+ end, Es).
+
+draw_arrow(DC, {X0,Y0}, {X1, Y1}, {X, Y}, Pen, Brush) ->
+ Xdiff = (X0 - X1) / 4,
+ Ydiff = (Y0 - Y1) / 4,
+ X2 = X1 + Xdiff + X,
+ Y2 = Y1 + Ydiff + Y,
+ wxDC:setPen(DC, Pen),
+ wxDC:setBrush(DC, Brush),
+
+ draw_line(DC, {X0,Y0}, {X1, Y1}, {X, Y}),
+
+ %% Draw arrow head
+ Radians = calc_angle({X0, Y0}, {X1, Y1}),
+ Len = 10,
+ %% Angle = 30,
+ %% Degrees = radians_to_degrees(Radians),
+ %% Radians2 = degrees_to_radians(Degrees + Angle + 180),
+ %% Radians3 = degrees_to_radians(Degrees - Angle + 180),
+ Radians2 = Radians + 3.665191429188092,
+ Radians3 = Radians + 2.617993877991494,
+ {X3, Y3} = calc_point({X2, Y2}, Len, Radians2),
+ {X4, Y4} = calc_point({X2, Y2}, Len, Radians3),
+ Points = [{round(X2), round(Y2)},
+ {round(X3), round(Y3)},
+ {round(X4), round(Y4)}],
+ wxDC:drawPolygon(DC, Points, []).
+
+draw_line(DC, {X0,Y0}, {X1, Y1}, {X, Y}) ->
+ wxDC:drawLine(DC, {round(X0 + X), round(Y0 + Y)}, {round(X1 + X), round(Y1 + Y)}).
+
+draw_vs(DC, Vs, {Xo, Yo}, Pen, Brush) ->
+ reltool_fgraph:foreach(fun({Key, #fg_v{ p ={X, Y}, color = Color, selected = Sel}}) ->
+ String = s(Key),
+ case Sel of
+ true ->
+ wxPen:setColour(Pen, ?color_fg),
+ wxBrush:setColour(Brush, ?color_bg),
+ wxDC:setPen(DC,Pen),
+ wxDC:setBrush(DC, Brush),
+ SelProps = {round(X-12 + Xo), round(Y-12 + Yo), 24, 24},
+ wxDC:drawRoundedRectangle(DC, SelProps, float(?ARC_R)),
+ ok;
+ false ->
+ ok
+ end,
+ case Color of
+ default ->
+ wxPen:setColour(Pen, ?color_default),
+ wxBrush:setColour(Brush, ?color_default_bg);
+ alternate ->
+ wxPen:setColour(Pen, ?color_alternate),
+ wxBrush:setColour(Brush, ?color_alternate_bg);
+ {FgColor, BgColor} ->
+ wxPen:setColour(Pen, FgColor),
+ wxBrush:setColour(Brush, BgColor);
+ Color ->
+ wxPen:setColour(Pen, Color),
+ wxBrush:setColour(Brush, Color)
+ end,
+ wxDC:setPen(DC,Pen),
+ wxDC:setBrush(DC, Brush),
+ NodeProps = {round(X-8 + Xo),round(Y-8 + Yo),17,17},
+ wxDC:drawRoundedRectangle(DC, NodeProps, float(?ARC_R)),
+ wxDC:drawText(DC, String, {round(X + Xo), round(Y + Yo)}),
+ ok;
+ (_) ->
+ ok
+ end,
+ Vs).
+
+draw_text(DC, Nvs, Nes, _KE) ->
+ VsString = "#nodes: " ++ integer_to_list(Nvs),
+ EsString = "#links: " ++ integer_to_list(Nes),
+ %% KEString = " ke: " ++ s(KE),
+ wxDC:drawText(DC, VsString, {10,10}),
+ wxDC:drawText(DC, EsString, {10,25}),
+ %% wxDC:drawText(DC, KEString, {10,40}),
+ ok.
+
+s(Format, Terms) -> lists:flatten(io_lib:format(Format, Terms)).
+s(Term) when is_float(Term) -> s("~.2f", [Term]);
+s(Term) when is_integer(Term) -> integer_to_list(Term);
+s(Term) when is_atom(Term) -> atom_to_list(Term);
+s(Term) -> s("~p", [Term]).
+
+%% Calclulate angle in radians for a line between two points
+calc_angle({X1, Y1}, {X2, Y2}) ->
+ math:atan2((Y2 - Y1), (X2 - X1)).
+
+%% Calc new point at a given distance and angle from another point
+calc_point({X, Y}, Length, Radians) ->
+ X2 = round(X + Length * math:cos(Radians)),
+ Y2 = round(Y + Length * math:sin(Radians)),
+ {X2, Y2}.
+
+%% %% Convert from an angle in radians to degrees
+%% radians_to_degrees(Radians) ->
+%% Radians * 180 / math:pi().
+%%
+%% %% Convert from an angle in degrees to radians
+%% degrees_to_radians(Degrees) ->
+%% Degrees * math:pi() / 180.
diff --git a/lib/reltool/src/reltool_mod_win.erl b/lib/reltool/src/reltool_mod_win.erl
new file mode 100644
index 0000000000..c05f73cde8
--- /dev/null
+++ b/lib/reltool/src/reltool_mod_win.erl
@@ -0,0 +1,773 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(reltool_mod_win).
+
+%% Public
+-export([start_link/5, raise/1, refresh/1]).
+
+%% Internal
+-export([init/6, loop/1]).
+
+%% sys callback functions
+-export([
+ system_continue/3,
+ system_terminate/4,
+ system_code_change/4
+ ]).
+
+-include_lib("wx/include/wx.hrl").
+-include("reltool.hrl").
+
+-record(state,
+ {parent_pid,
+ xref_pid,
+ rel_pid,
+ mod_wins,
+ sys,
+ common,
+ mod,
+ name,
+ frame,
+ panel,
+ book,
+ status_bar,
+ deps_used_by_ctrl,
+ deps_uses_ctrl,
+ popup_menu,
+ active_page,
+ code_pages}).
+
+-record(code_page,
+ {name,
+ editor,
+ find_objs,
+ find_data}).
+
+-record(find_objs,
+ {search, % Search input ctrl
+ goto, % Goto input ctrl
+ radio}). % Radio buttons
+
+-record(find_data,
+ {start, % start pos
+ found, % status
+ history}). % list of recent positions
+
+-define(WIN_WIDTH, 800).
+-define(WIN_HEIGHT, 600).
+
+-define(CLOSE_ITEM, ?wxID_EXIT). %% Use OS specific version if available
+-define(ABOUT_ITEM, ?wxID_ABOUT). %% Use OS specific
+-define(CONTENTS_ITEM, 300).
+-define(SEARCH_ENTRY, 413).
+-define(GOTO_ENTRY, 414).
+
+-define(MODS_MOD_COL, 0).
+-define(MODS_APP_COL, 1).
+
+-define(INITIAL_CODE_PAGE_NAME, "Code").
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Client
+
+start_link(WxEnv, Xref, RelPid, Common, ModName) ->
+ proc_lib:start_link(?MODULE, init, [self(), WxEnv, Xref, RelPid, Common, ModName], infinity, []).
+
+raise(Pid) ->
+ reltool_utils:cast(Pid, raise).
+
+refresh(Pid) ->
+ reltool_utils:cast(Pid, refresh).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Server
+
+init(Parent, WxEnv, Xref, RelPid, C, ModName) ->
+ try
+ do_init(Parent, WxEnv, Xref, RelPid, C, ModName)
+ catch
+ error:Reason ->
+ exit({Reason, erlang:get_stacktrace()})
+ end.
+
+do_init(Parent, WxEnv, Xref, RelPid, C, ModName) ->
+ process_flag(trap_exit, C#common.trap_exit),
+ {ok, Mod} = reltool_server:get_mod(Xref, ModName),
+ {ok, Sys} = reltool_server:get_sys(Xref),
+ S = #state{parent_pid = Parent,
+ xref_pid = Xref,
+ rel_pid = RelPid,
+ sys = Sys,
+ mod = Mod,
+ name = atom_to_list(Mod#mod.name),
+ common = C},
+ proc_lib:init_ack(Parent, {ok, self()}),
+ wx:set_env(WxEnv),
+ wx:debug(C#common.wx_debug),
+ S2 = wx:batch(fun() -> create_window(S) end),
+ loop(S2).
+
+loop(#state{xref_pid = Xref, common = C, mod = Mod} = S) ->
+ receive
+ Msg ->
+ %% io:format("~s~p -> ~p\n", [S#state.name, self(), Msg]),
+ case Msg of
+ {system, From, SysMsg} ->
+ Dbg = C#common.sys_debug,
+ sys:handle_system_msg(SysMsg, From, S#state.parent_pid, ?MODULE, Dbg, S);
+ {cast, _From, raise} ->
+ wxFrame:raise(S#state.frame),
+ wxFrame:setFocus(S#state.frame),
+ ?MODULE:loop(S);
+ {cast, _From, refresh} ->
+ %% wx_misc:beginBusyCursor(),
+ case reltool_server:get_mod(Xref, Mod#mod.name) of
+ {ok, Mod2} ->
+ {ok, Sys} = reltool_server:get_sys(Xref),
+ S2 = redraw_window(S#state{sys = Sys, mod = Mod2}),
+ %% wx_misc:endBusyCursor(),
+ ?MODULE:loop(S2);
+ {error, _} ->
+ wxFrame:destroy(S#state.frame),
+ exit(shutdown)
+ end;
+ {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid ->
+ exit(Reason);
+ #wx{event = #wxSize{}} = Wx ->
+ Wx2 = reltool_utils:get_latest_resize(Wx),
+ S2 = handle_event(S, Wx2),
+ ?MODULE:loop(S2);
+ #wx{obj = ObjRef,
+ event = #wxClose{type = close_window}} ->
+ wxFrame:destroy(ObjRef),
+ exit(shutdown);
+ #wx{} = Wx ->
+ S2 = handle_event(S, Wx),
+ ?MODULE:loop(S2);
+ _ ->
+ error_logger:format("~p~p got unexpected message:\n\t~p\n",
+ [?MODULE, self(), Msg]),
+ ?MODULE:loop(S)
+ end
+ end.
+
+create_window(#state{mod = Mod, name = ModStr} = S) ->
+ Title = atom_to_list(?APPLICATION) ++ " - " ++
+ atom_to_list(Mod#mod.app_name) ++ " - " ++
+ ModStr ++ ".erl",
+ Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, []),
+ %% wxFrame:setSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}),
+ Panel = wxPanel:new(Frame, []),
+ StatusBar = wxFrame:createStatusBar(Frame,[]),
+
+ Book = wxNotebook:new(Panel, ?wxID_ANY, []),
+
+ S2 = S#state{frame = Frame,
+ panel = Panel,
+ book = Book,
+ status_bar = StatusBar,
+ code_pages = []},
+ S3 = create_deps_page(S2),
+ S4 = create_code_page(S3, ?INITIAL_CODE_PAGE_NAME),
+ S5 = create_config_page(S4),
+ wxNotebook:setSelection(Book, 0),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Sizer, Book, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ wxPanel:setSizer(Panel, Sizer),
+ wxSizer:fit(Sizer, Frame),
+ wxSizer:setSizeHints(Sizer, Frame),
+
+ wxEvtHandler:connect(Book, command_notebook_page_changed, [{skip, true}]),
+ wxFrame:connect(Frame, close_window),
+ wxFrame:show(Frame),
+
+ S5.
+
+create_deps_page(S) ->
+ Panel = wxPanel:new(S#state.book, []),
+ Main = wxBoxSizer:new(?wxHORIZONTAL),
+
+ UsedByCtrl = create_mods_list_ctrl(Panel, Main, "Modules used by others", " and their applications"),
+ wxSizer:add(Main,
+ wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ UsesCtrl = create_mods_list_ctrl(Panel, Main, "Used modules", " and their applications"),
+ S2 = S#state{deps_used_by_ctrl = UsedByCtrl,
+ deps_uses_ctrl = UsesCtrl},
+ redraw_mods(S2),
+ wxPanel:setSizer(Panel, Main),
+ wxNotebook:addPage(S2#state.book, Panel, "Dependencies", []),
+ S2.
+
+create_mods_list_ctrl(Panel, Sizer, ModText, AppText) ->
+ Width = lists:max([100, ?WIN_WIDTH - 40]) div 2,
+ Height = lists:max([100, ?WIN_HEIGHT - 100]),
+ ListCtrl = wxListCtrl:new(Panel,
+ [{style,
+ ?wxLC_REPORT bor
+ %% ?wxLC_SORT_ASCENDING bor
+ ?wxLC_SINGLE_SEL bor
+ ?wxHSCROLL bor
+ ?wxVSCROLL},
+ {size, {Width, Height}}]),
+ %% Prep images
+ reltool_utils:assign_image_list(ListCtrl),
+
+ %% Prep column label
+ ListItem = wxListItem:new(),
+ wxListItem:setAlign(ListItem, ?wxLIST_FORMAT_LEFT),
+ wxListItem:setText(ListItem, ModText),
+ wxListCtrl:insertColumn(ListCtrl, ?MODS_MOD_COL, ListItem),
+ %% wxListCtrl:setColumnWidth(ListCtrl, ?MODS_MOD_COL, ?MODS_MOD_COL_WIDTH),
+
+ wxListItem:setText(ListItem, AppText),
+ wxListCtrl:insertColumn(ListCtrl, ?MODS_APP_COL, ListItem),
+ %% wxListCtrl:setColumnWidth(ListCtrl, ?MODS_APP_COL, ?MODS_APP_COL_WIDTH),
+ wxListItem:destroy(ListItem),
+
+ wxEvtHandler:connect(ListCtrl, size, [{skip, true}, {userData, mods_list_ctrl}]),
+ wxListCtrl:connect(ListCtrl, command_list_item_activated, [{userData, open_app}]),
+ wxWindow:connect(ListCtrl, enter_window),
+
+ wxSizer:add(Sizer, ListCtrl,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+ ListCtrl.
+
+create_code_page(#state{book = Book, code_pages = Pages, name = ModStr} = S, PageName) ->
+ case find_page(S, PageName) of
+ not_found ->
+ Page = do_create_code_page(S, PageName),
+ Pages2 = Pages ++ [Page],
+ Pos = length(Pages2),
+ wxNotebook:setSelection(Book, Pos),
+ case find_page(S, ?INITIAL_CODE_PAGE_NAME) of
+ not_found ->
+ ignore;
+ {found, _, CodePos} ->
+ %% Rename initial code page
+ wxNotebook:setPageText(Book, CodePos, ModStr)
+ end,
+ S#state{active_page = Page, code_pages = Pages2};
+ {found, Page, Pos} ->
+ wxNotebook:setSelection(Book, Pos),
+ S#state{active_page = Page}
+ end.
+
+find_page(S, PageName) ->
+ find_page(S#state.code_pages, PageName, 1).
+
+find_page([Page | Pages], PageName, Pos) ->
+ case Page#code_page.name =:= PageName of
+ true ->
+ {found, Page, Pos};
+ false ->
+ find_page(Pages, PageName, Pos + 1)
+ end;
+find_page([], _PageName, _Pos) ->
+ not_found.
+
+do_create_code_page(#state{xref_pid = Xref, mod = M} = S, PageName) ->
+ Panel = wxPanel:new(S#state.book, []),
+ Editor = create_editor(Panel),
+ ToolTip = "Double click on a function call to search the function definition.",
+ wxBitmapButton:setToolTip(Editor, ToolTip),
+ {Objs, Data, SearchSz} = create_search_area(Panel),
+
+ {ok, App} = reltool_server:get_app(Xref, M#mod.app_name),
+ ErlBin =
+ case App#app.is_escript of
+ true -> find_escript_bin(App, M);
+ false -> find_regular_bin(App, M)
+ end,
+
+ load_code(Editor, ErlBin),
+
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Sizer, Editor, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(Sizer, SearchSz, [{flag, ?wxEXPAND}]),
+ wxPanel:setSizer(Panel, Sizer),
+ wxNotebook:addPage(S#state.book, Panel, PageName, []),
+ #code_page{name = PageName, editor = Editor, find_objs = Objs, find_data = Data}.
+
+find_regular_bin(App, Mod) ->
+ ActiveDir = App#app.active_dir,
+ SrcDir = filename:join([ActiveDir, "src"]),
+ ModStr = atom_to_list(Mod#mod.name),
+ Base = ModStr ++ ".erl",
+ Find = fun(F, _Acc) -> file:read_file(F) end,
+ case filelib:fold_files(SrcDir, Base, true, Find, {error, enoent}) of
+ {ok, Bin} ->
+ Bin;
+ {error, enoent} ->
+ %% Reconstructing the source code from debug info if possible
+ BeamFile = filename:join([ActiveDir, "ebin", ModStr ++ ".beam"]),
+ case beam_lib:chunks(BeamFile, [abstract_code]) of
+ {ok,{_,[{abstract_code,{_,AC}}]}} ->
+ list_to_binary(erl_prettypr:format(erl_syntax:form_list(AC)));
+ _ ->
+ list_to_binary(["%% Bad luck, cannot find any debug info in the file \"", BeamFile])
+ end
+ end.
+
+find_escript_bin(#app{active_dir = ActiveDir}, Mod) ->
+ NotFound = false,
+ ModName = Mod#mod.name,
+ {Fun, Escript} =
+ case filelib:is_regular(ActiveDir) of
+ true ->
+ %% File is on top level in the escript
+ {fun(FullName, _GetInfo, GetBin, Acc) ->
+ case filename:split(FullName) of
+ [_] ->
+ Bin = GetBin(),
+ case beam_lib:version(Bin) of
+ {ok,{M, _}} when M =:= ModName; FullName =:= "." ->
+ case beam_lib:chunks(Bin, [abstract_code]) of
+ {ok,{_,[{abstract_code,{_,AC}}]}} ->
+ {obj, list_to_binary(erl_prettypr:format(erl_syntax:form_list(AC)))};
+ _ ->
+ Acc
+ end;
+ _ ->
+ Acc
+ end;
+ _ ->
+ Acc
+ end
+ end,
+ ActiveDir};
+ false ->
+ %% File is in an archive
+ Ext = code:objfile_extension(),
+ SrcFile = lists:concat([ModName, ".erl"]),
+ ObjFile = lists:concat([ModName, Ext]),
+ {fun(FullName, _GetInfo, GetBin, Acc) ->
+ io:format("", []),
+ case filename:split(FullName) of
+ [_AppName, "ebin", F] when F =:= ObjFile, Acc =:= NotFound ->
+ case beam_lib:chunks(GetBin(), [abstract_code]) of
+ {ok,{_,[{abstract_code,{_,AC}}]}} ->
+ {obj, list_to_binary(erl_prettypr:format(erl_syntax:form_list(AC)))};
+ _ ->
+ Acc
+ end;
+ [_AppName, "src", F] when F =:= SrcFile ->
+ {text, GetBin()};
+ _ ->
+ Acc
+ end
+ end,
+ filename:dirname(ActiveDir)}
+ end,
+ try
+ case escript:foldl(Fun, NotFound, Escript) of
+ {ok, {text, Bin}} ->
+ Bin;
+ {ok, {obj, Bin}} ->
+ Bin;
+ _ ->
+ list_to_binary(["%% Bad luck, cannot find the code in the escript ", Escript, "."])
+ end
+ catch
+ throw:Reason when is_list(Reason) ->
+ list_to_binary(["%% Bad luck, cannot find the code in the escript ", Escript, ": ", Reason])
+ end.
+
+create_config_page(S) ->
+ S.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_event(#state{xref_pid = Xref} = S, Wx) ->
+ %% io:format("wx: ~p\n", [Wx]),
+ case Wx of
+ #wx{obj= ListCtrl, userData = mods_list_ctrl, event = #wxSize{type = size, size = {W, _H}}} ->
+ wxListCtrl:setColumnWidth(ListCtrl, ?MODS_MOD_COL, (2 * W) div 3),
+ wxListCtrl:setColumnWidth(ListCtrl, ?MODS_APP_COL, W div 3),
+ S;
+ #wx{userData = open_app,
+ obj = ListCtrl,
+ event = #wxList{type = command_list_item_activated, itemIndex = Pos}} ->
+ ModStr = wxListCtrl:getItemText(ListCtrl, Pos),
+ ModName = list_to_atom(ModStr),
+ {ok, Mod} = reltool_server:get_mod(Xref, ModName),
+ S2 = create_code_page(S#state{mod = Mod}, ModStr),
+ find_regexp_forward(S2, S2#state.name ++ ":");
+ %% ok = reltool_sys_win:open_app(S#state.rel_pid, Mod#mod.app_name),
+ %% S;
+ #wx{obj = Editor,
+ event = #wxStyledText{type = stc_doubleclick}} ->
+ goto_function(S, Editor);
+ #wx{id = ?SEARCH_ENTRY,
+ event = #wxCommand{type = command_text_enter, cmdString = Str}} ->
+ find_string(S, Str);
+ #wx{id = ?GOTO_ENTRY,
+ event = #wxCommand{type = command_text_enter, cmdString = Str}} ->
+ goto_line(S, Str);
+ #wx{event = #wxNotebook{type = command_notebook_page_changed}} ->
+ case wxNotebook:getSelection(S#state.book) of
+ 0 -> % Deps page
+ S;
+ N -> % Code page
+ Page = lists:nth(N, S#state.code_pages),
+ S#state{active_page = Page}
+ end;
+ #wx{event = #wxCommand{type = command_button_clicked}, userData = history_back} ->
+ goto_back(S);
+ #wx{obj = ObjRef, event = #wxMouse{type = enter_window}} ->
+ wxWindow:setFocus(ObjRef),
+ S;
+ _ ->
+ error_logger:format("~p~p got unexpected mod event from wx:\n\t~p\n",
+ [?MODULE, self(), Wx]),
+ S
+ end.
+
+redraw_mods(#state{xref_pid = Xref,
+ deps_used_by_ctrl = UsedByCtrl,
+ deps_uses_ctrl = UsesCtrl,
+ mod = #mod{is_pre_included = IsPre,
+ is_included = IsIncl,
+ uses_mods = UsesModNames,
+ used_by_mods = UsedByModNames},
+ status_bar = Bar}) ->
+ InclStatus =
+ case IsIncl of
+ true when IsPre =:= true -> "Whitelist - ";
+ true -> "Derived - ";
+ false -> "Blacklist - ";
+ undefined -> "Source - "
+ end,
+ Status = lists:concat([InclStatus,
+ " uses ", length(UsesModNames), " modules and ",
+ " is used by ", length(UsedByModNames), " modules."]),
+ wxStatusBar:setStatusText(Bar, Status),
+ UsesMods = [select_image(Xref, M) || M <- UsesModNames],
+ UsedByMods = [select_image(Xref, M) || M <- UsedByModNames],
+ redraw_mods(UsedByCtrl, UsedByMods),
+ redraw_mods(UsesCtrl, UsesMods).
+
+select_image(Xref, ModName) ->
+ {ok, M} = reltool_server:get_mod(Xref, ModName),
+ Image =
+ case M#mod.is_included of
+ _ when M#mod.app_name =:= ?MISSING_APP -> ?ERR_IMAGE;
+ true -> ?TICK_IMAGE;
+ false -> ?WARN_IMAGE;
+ undefined -> ?ERR_IMAGE
+ end,
+ {Image, M#mod.app_name, M}.
+
+redraw_mods(ListCtrl, []) ->
+ wxListCtrl:deleteAllItems(ListCtrl);
+redraw_mods(ListCtrl, ImageMods) ->
+ wxListCtrl:deleteAllItems(ListCtrl),
+ Add =
+ fun({ImageId, AppName, #mod{name = ModName}}, Row) ->
+ wxListCtrl:insertItem(ListCtrl, Row, ""),
+ if (Row rem 2) =:= 0 ->
+ wxListCtrl:setItemBackgroundColour(ListCtrl, Row, {240,240,255});
+ true ->
+ ignore
+ end,
+ wxListCtrl:setItem(ListCtrl, Row, ?MODS_MOD_COL,
+ atom_to_list(ModName), [{imageId, ImageId}]),
+ wxListCtrl:setItem(ListCtrl, Row, ?MODS_APP_COL,
+ atom_to_list(AppName), [{imageId, ImageId}]),
+ Row + 1
+ end,
+ wx:foldl(Add, 0, lists:sort(ImageMods)).
+
+redraw_config(S) ->
+ S.
+
+redraw_window(S) ->
+ redraw_config(S),
+ redraw_mods(S),
+ S.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+goto_line(#state{active_page = P} = S, LineNo) when is_integer(LineNo) ->
+ Editor = P#code_page.editor,
+ wxStyledTextCtrl:gotoLine(Editor, LineNo),
+ Left = wxStyledTextCtrl:getCurrentPos(Editor),
+ Right = wxStyledTextCtrl:getLineEndPosition(Editor, LineNo),
+ wxStyledTextCtrl:setSelection(Editor, Left, Right),
+ S;
+goto_line(#state{active_page = P} =S, Str) when is_list(Str) ->
+ try
+ LineNo = list_to_integer(Str),
+ CurrentPos = wxStyledTextCtrl:getCurrentPos(P#code_page.editor),
+ S2 = add_pos_to_history(S, CurrentPos),
+ goto_line(S2, LineNo - 1)
+ catch
+ _:_ ->
+ wxStatusBar:setStatusText(S#state.status_bar, "Not a line number"),
+ S
+ end.
+
+find_string(S, Str) ->
+ find_string(S, Str, 0).
+
+find_regexp_forward(S, Str) ->
+ S2 = find_string(S, Str, ?wxSTC_FIND_REGEXP),
+ TextCtrl = ((S2#state.active_page)#code_page.find_objs)#find_objs.search,
+ wxTextCtrl:setValue(TextCtrl, Str),
+ S2.
+
+find_string(#state{active_page = #code_page{editor = Editor,
+ find_objs = #find_objs{radio={NextO,_,CaseO}},
+ find_data = #find_data{found = Found} = Data} = P} = S,
+ Str,
+ Flag) ->
+ wxStyledTextCtrl:hideSelection(Editor, true),
+ Dir = wxRadioButton:getValue(NextO) xor wx_misc:getKeyState(?WXK_SHIFT),
+ Case = wxCheckBox:getValue(CaseO),
+ Pos =
+ if
+ Found, Dir -> %% Forward Continuation
+ wxStyledTextCtrl:getAnchor(Editor);
+ Found -> %% Backward Continuation
+ wxStyledTextCtrl:getCurrentPos(Editor);
+ Dir -> %% Forward wrap
+ 0;
+ true -> %% Backward wrap
+ wxStyledTextCtrl:getLength(Editor)
+ end,
+ wxStyledTextCtrl:gotoPos(Editor,Pos),
+ wxStyledTextCtrl:searchAnchor(Editor),
+ Flag2 =
+ if Case -> Flag bor ?wxSTC_FIND_MATCHCASE;
+ true -> Flag
+ end,
+ Res =
+ if
+ Dir -> wxStyledTextCtrl:searchNext(Editor, Flag2, Str);
+ true -> wxStyledTextCtrl:searchPrev(Editor, Flag2, Str)
+ end,
+ Found2 =
+ case Res >= 0 of
+ true ->
+ wxStyledTextCtrl:hideSelection(Editor, false),
+ %% io:format("Found ~p ~n",[Res]),
+ LineNo = wxStyledTextCtrl:lineFromPosition(Editor,Res),
+ wxStyledTextCtrl:scrollToLine(Editor, LineNo - 3),
+ wxStatusBar:setStatusText(S#state.status_bar, ""),
+ true;
+ false ->
+ wxStatusBar:setStatusText(S#state.status_bar,
+ "Not found (Hit Enter to wrap search)"),
+ false
+ end,
+ P2 = P#code_page{find_data = Data#find_data{found = Found2}},
+ Pages = lists:keystore(P#code_page.name, #code_page.name, S#state.code_pages, P2),
+ S#state{active_page = P2, code_pages = Pages}.
+
+goto_function(S, Editor) ->
+ wxStyledTextCtrl:hideSelection(Editor, false),
+ CurrentPos = wxStyledTextCtrl:getCurrentPos(Editor),
+ Left = wxStyledTextCtrl:wordStartPosition(Editor, CurrentPos, true),
+ Right = wxStyledTextCtrl:wordEndPosition(Editor, CurrentPos, true),
+ ColonPos = Left - 1,
+ Left2 =
+ case wxStyledTextCtrl:getCharAt(Editor, ColonPos) of
+ $: ->
+ wxStyledTextCtrl:wordStartPosition(Editor, ColonPos, true);
+ _ ->
+ Left
+ end,
+ Right2 =
+ case wxStyledTextCtrl:getCharAt(Editor, Right) of
+ $: ->
+ wxStyledTextCtrl:wordEndPosition(Editor, Right + 1, true);
+ _ ->
+ Right
+ end,
+ case [wxStyledTextCtrl:getCharAt(Editor, Right2)] of
+ "(" ->
+ wxStyledTextCtrl:setSelection(Editor, Left2, Right2),
+ Text = wxStyledTextCtrl:getSelectedText(Editor),
+ S2 = add_pos_to_history(S, CurrentPos),
+ do_goto_function(S2, string:tokens(Text, ":"));
+ _ ->
+ %% No function call
+ wxStyledTextCtrl:hideSelection(Editor, false),
+ wxStyledTextCtrl:setSelection(Editor, Left2, Right2),
+ S
+ end.
+
+do_goto_function(S, []) ->
+ S;
+do_goto_function(#state{active_page = P} = S, [FunName]) ->
+ wxStyledTextCtrl:gotoPos(P#code_page.editor, 1),
+ find_regexp_forward(S, "^" ++ FunName ++ "(");
+do_goto_function(S, [ModStr, FunStr]) ->
+ case reltool_server:get_mod(S#state.xref_pid, list_to_atom(ModStr)) of
+ {ok, Mod} when Mod#mod.app_name =/= ?MISSING_APP ->
+ S2 = create_code_page(S#state{mod = Mod}, ModStr),
+ find_regexp_forward(S2, "^" ++ FunStr ++ "(");
+ {ok, _} ->
+ wxStatusBar:setStatusText(S#state.status_bar, "No such module: " ++ ModStr),
+ S
+ end.
+
+goto_back(#state{active_page = #code_page{editor = Editor, find_data = Data} = Page,
+ code_pages = Pages} = S) ->
+ case Data#find_data.history of
+ [PrevPos | History] ->
+ LineNo = wxStyledTextCtrl:lineFromPosition(Editor, PrevPos),
+ Data2 = Data#find_data{history = History},
+ Page2 = Page#code_page{find_data = Data2},
+ Pages2 = lists:keystore(Page2#code_page.name, #code_page.name, Pages, Page2),
+ goto_line(S#state{active_page = Page2, code_pages = Pages2}, LineNo);
+ [] ->
+ wxStatusBar:setStatusText(S#state.status_bar, "No history"),
+ S
+ end.
+
+add_pos_to_history(#state{active_page = Page, code_pages = Pages} = S, CurrentPos) ->
+ Data = Page#code_page.find_data,
+ Data2 = Data#find_data{history = [CurrentPos | Data#find_data.history]},
+ Page2 = Page#code_page{find_data = Data2},
+ Pages2 = lists:keystore(Page2#code_page.name, #code_page.name, Pages, Page2),
+ S#state{active_page = Page2, code_pages = Pages2}.
+
+create_editor(Parent) ->
+ FixedFont = wxFont:new(10, ?wxFONTFAMILY_TELETYPE, ?wxNORMAL, ?wxNORMAL,[]),
+ %%Ed = wxStyledTextCtrl:new(Parent, [{size, {700, 500}}]),
+ Ed = wxStyledTextCtrl:new(Parent),
+
+ wxStyledTextCtrl:styleClearAll(Ed),
+ wxStyledTextCtrl:styleSetFont(Ed, ?wxSTC_STYLE_DEFAULT, FixedFont),
+ wxStyledTextCtrl:setLexer(Ed, ?wxSTC_LEX_ERLANG),
+ wxStyledTextCtrl:setMarginType(Ed, 0, ?wxSTC_MARGIN_NUMBER),
+ LW = wxStyledTextCtrl:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, "9"),
+ wxStyledTextCtrl:setMarginWidth(Ed, 0, LW),
+
+ wxStyledTextCtrl:setSelectionMode(Ed, ?wxSTC_SEL_LINES),
+ %%wxStyledTextCtrl:hideSelection(Ed, true),
+
+ Styles = [{?wxSTC_ERLANG_DEFAULT, {0,0,0}},
+ {?wxSTC_ERLANG_COMMENT, {160,53,35}},
+ {?wxSTC_ERLANG_VARIABLE, {150,100,40}},
+ {?wxSTC_ERLANG_NUMBER, {5,5,100}},
+ {?wxSTC_ERLANG_KEYWORD, {130,40,172}},
+ {?wxSTC_ERLANG_STRING, {170,45,132}},
+ {?wxSTC_ERLANG_OPERATOR, {30,0,0}},
+ {?wxSTC_ERLANG_ATOM, {0,0,0}},
+ {?wxSTC_ERLANG_FUNCTION_NAME, {64,102,244}},
+ {?wxSTC_ERLANG_CHARACTER,{236,155,172}},
+ {?wxSTC_ERLANG_MACRO, {40,144,170}},
+ {?wxSTC_ERLANG_RECORD, {40,100,20}},
+ {?wxSTC_ERLANG_SEPARATOR,{0,0,0}},
+ {?wxSTC_ERLANG_NODE_NAME,{0,0,0}}],
+ SetStyle = fun({Style, Color}) ->
+ wxStyledTextCtrl:styleSetFont(Ed, Style, FixedFont),
+ wxStyledTextCtrl:styleSetForeground(Ed, Style, Color)
+ end,
+ [SetStyle(Style) || Style <- Styles],
+ wxStyledTextCtrl:setKeyWords(Ed, 0, keyWords()),
+
+ %% Margins Markers
+ %% Breakpoint Should be a pixmap?
+ wxStyledTextCtrl:markerDefine(Ed, 0, ?wxSTC_MARK_CIRCLE, [{foreground, {170,20,20}}]),
+ wxStyledTextCtrl:markerDefine(Ed, 0, ?wxSTC_MARK_CIRCLE, [{background, {200,120,120}}]),
+ %% Disabled Breakpoint
+ wxStyledTextCtrl:markerDefine(Ed, 1, ?wxSTC_MARK_CIRCLE, [{foreground, {20,20,170}}]),
+ wxStyledTextCtrl:markerDefine(Ed, 1, ?wxSTC_MARK_CIRCLE, [{background, {120,120,200}}]),
+
+ %% Current Line
+ wxStyledTextCtrl:markerDefine(Ed, 2, ?wxSTC_MARK_ARROW, [{foreground, {20,170,20}}]),
+ wxStyledTextCtrl:markerDefine(Ed, 2, ?wxSTC_MARK_ARROW, [{background, {200,255,200}}]),
+ wxStyledTextCtrl:markerDefine(Ed, 3, ?wxSTC_MARK_BACKGROUND, [{background, {200,255,200}}]),
+
+ %% Scrolling
+ Policy = ?wxSTC_CARET_SLOP bor ?wxSTC_CARET_JUMPS bor ?wxSTC_CARET_EVEN,
+ wxStyledTextCtrl:setYCaretPolicy(Ed, Policy, 3),
+ wxStyledTextCtrl:setVisiblePolicy(Ed, Policy, 3),
+
+ wxStyledTextCtrl:connect(Ed, stc_doubleclick),
+ wxWindow:connect(Ed, enter_window),
+
+ wxStyledTextCtrl:setReadOnly(Ed, true),
+ Ed.
+
+create_search_area(Parent) ->
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+ wxSizer:add(Sizer, wxStaticText:new(Parent, ?wxID_ANY, "Find:"),
+ [{flag,?wxALIGN_CENTER_VERTICAL}]),
+ TC1 = wxTextCtrl:new(Parent, ?SEARCH_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]),
+ wxSizer:add(Sizer, TC1, [{proportion,3}, {flag, ?wxEXPAND}]),
+ Nbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Next"),
+ wxRadioButton:setValue(Nbtn, true),
+ wxSizer:add(Sizer,Nbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]),
+ Pbtn = wxRadioButton:new(Parent, ?wxID_ANY, "Previous"),
+ wxSizer:add(Sizer,Pbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]),
+ Cbtn = wxCheckBox:new(Parent, ?wxID_ANY, "Match Case"),
+ wxSizer:add(Sizer,Cbtn,[{flag,?wxALIGN_CENTER_VERTICAL}]),
+ wxSizer:add(Sizer, 15,15, [{proportion,1}, {flag, ?wxEXPAND}]),
+ wxSizer:add(Sizer, wxStaticText:new(Parent, ?wxID_ANY, "Goto Line:"),
+ [{flag,?wxALIGN_CENTER_VERTICAL}]),
+ TC2 = wxTextCtrl:new(Parent, ?GOTO_ENTRY, [{style, ?wxTE_PROCESS_ENTER}]),
+ wxSizer:add(Sizer, TC2, [{proportion,0}, {flag, ?wxEXPAND}]),
+ Button = wxButton:new(Parent, ?wxID_ANY, [{label, "Back"}]),
+ wxSizer:add(Sizer, Button, []),
+
+ wxEvtHandler:connect(Button, command_button_clicked, [{userData, history_back}]),
+ %% wxTextCtrl:connect(TC1, command_text_updated),
+ wxTextCtrl:connect(TC1, command_text_enter),
+ %% wxTextCtrl:connect(TC1, kill_focus),
+ wxTextCtrl:connect(TC2, command_text_enter),
+ wxWindow:connect(Parent, command_button_clicked),
+ {#find_objs{search = TC1,goto = TC2,radio = {Nbtn,Pbtn,Cbtn}},
+ #find_data{start = 0, found = false, history = []},
+ Sizer}.
+
+load_code(Ed, Code) when is_binary(Code) ->
+ wxStyledTextCtrl:setReadOnly(Ed, false),
+ wxStyledTextCtrl:setTextRaw(Ed, <<Code/binary, 0:8>>),
+ Lines = wxStyledTextCtrl:getLineCount(Ed),
+ Sz = trunc(math:log10(Lines))+1,
+ LW = wxStyledTextCtrl:textWidth(Ed, ?wxSTC_STYLE_LINENUMBER, lists:duplicate(Sz, $9)),
+ %%io:format("~p ~p ~p~n", [Lines, Sz, LW]),
+ wxStyledTextCtrl:setMarginWidth(Ed, 0, LW+5),
+ wxStyledTextCtrl:setReadOnly(Ed, true),
+ Ed.
+
+keyWords() ->
+ L = ["after","begin","case","try","cond","catch","andalso","orelse",
+ "end","fun","if","let","of","query","receive","when","bnot","not",
+ "div","rem","band","and","bor","bxor","bsl","bsr","or","xor"],
+ lists:flatten([K ++ " " || K <- L] ++ [0]).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% sys callbacks
+
+system_continue(_Parent, _Debug, S) ->
+ ?MODULE:loop(S).
+
+system_terminate(Reason, _Parent, _Debug, _S) ->
+ exit(Reason).
+
+system_code_change(S,_Module,_OldVsn,_Extra) ->
+ {ok, S}.
diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl
new file mode 100644
index 0000000000..8d4530131f
--- /dev/null
+++ b/lib/reltool/src/reltool_server.erl
@@ -0,0 +1,1678 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(reltool_server).
+
+%% Public
+-export([
+ start_link/0, start_link/1,
+ get_config/3, load_config/2, save_config/4,
+ get_rel/2, get_script/2,
+ reset_config/1, undo_config/1,
+ get_mod/2,
+ get_app/2, set_app/2,
+ get_apps/2, set_apps/2,
+ get_sys/1, set_sys/2,
+ get_status/1,
+ gen_rel_files/2, gen_target/2, gen_spec/1
+ ]).
+
+%% Internal
+-export([init/1, loop/1]).
+
+%% sys callback functions
+-export([
+ system_continue/3,
+ system_terminate/4,
+ system_code_change/4
+ ]).
+
+-include("reltool.hrl").
+
+-record(state,
+ {options,
+ parent_pid,
+ common,
+ sys,
+ old_sys,
+ status,
+ old_status}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Client
+
+start_link() ->
+ start_link([]).
+
+start_link(Options) ->
+ proc_lib:start_link(?MODULE, init, [[{parent, self()} | Options]], infinity, []).
+
+get_config(Pid, InclDefaults, InclDerivates) ->
+ reltool_utils:call(Pid, {get_config, InclDefaults, InclDerivates}).
+
+load_config(Pid, FilenameOrConfig) ->
+ reltool_utils:call(Pid, {load_config, FilenameOrConfig}).
+
+save_config(Pid, Filename, InclDefaults, InclDerivates) ->
+ reltool_utils:call(Pid, {save_config, Filename, InclDefaults, InclDerivates}).
+
+reset_config(Pid) ->
+ reltool_utils:call(Pid, reset_config).
+
+undo_config(Pid) ->
+ reltool_utils:call(Pid, undo_config).
+
+get_rel(Pid, RelName) ->
+ reltool_utils:call(Pid, {get_rel, RelName}).
+
+get_script(Pid, RelName) ->
+ reltool_utils:call(Pid, {get_script, RelName}).
+
+get_mod(Pid, ModName) ->
+ reltool_utils:call(Pid, {get_mod, ModName}).
+
+get_app(Pid, AppName) ->
+ reltool_utils:call(Pid, {get_app, AppName}).
+
+set_app(Pid, App) ->
+ reltool_utils:call(Pid, {set_app, App}).
+
+get_apps(Pid, Kind) ->
+ reltool_utils:call(Pid, {get_apps, Kind}).
+
+set_apps(Pid, Apps) ->
+ reltool_utils:call(Pid, {set_apps, Apps}).
+
+get_sys(Pid) ->
+ reltool_utils:call(Pid, get_sys).
+
+set_sys(Pid, Sys) ->
+ reltool_utils:call(Pid, {set_sys, Sys}).
+
+get_status(Pid) ->
+ reltool_utils:call(Pid, get_status).
+
+gen_rel_files(Pid, Dir) ->
+ reltool_utils:call(Pid, {gen_rel_files, Dir}).
+
+gen_target(Pid, Dir) ->
+ reltool_utils:call(Pid, {gen_target, Dir}).
+
+gen_spec(Pid) ->
+ reltool_utils:call(Pid, gen_spec).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Server
+
+init(Options) ->
+ try
+ do_init(Options)
+ catch
+ error:Reason ->
+ exit({Reason, erlang:get_stacktrace()})
+ end.
+
+do_init(Options) ->
+ case parse_options(Options) of
+ {#state{parent_pid = ParentPid, common = C, sys = Sys} = S, Status} ->
+ %% process_flag(trap_exit, (S#state.common)#common.trap_exit),
+ proc_lib:init_ack(ParentPid, {ok, self(), C, Sys#sys{apps = undefined}}),
+ {S2, Status2} = refresh(S, true, Status),
+ {S3, Status3} = analyse(S2#state{old_sys = S2#state.sys}, Status2),
+ case Status3 of
+ {ok, _Warnings} ->
+ loop(S3#state{status = Status3, old_status = {ok, []}});
+ {error, Reason} ->
+ exit(Reason)
+ end
+ end.
+
+parse_options(Opts) ->
+ AppTab = ets:new(reltool_apps, [public, ordered_set, {keypos, #app.name}]),
+ ModTab = ets:new(reltool_mods, [public, ordered_set, {keypos, #mod.name}]),
+ ModUsesTab = ets:new(reltool_mod_uses, [public, bag, {keypos, 1}]),
+ Sys = #sys{root_dir = reltool_utils:root_dir(),
+ lib_dirs = reltool_utils:erl_libs(),
+ escripts = [],
+ incl_cond = ?DEFAULT_INCL_COND,
+ mod_cond = ?DEFAULT_MOD_COND,
+ apps = ?DEFAULT_APPS,
+ boot_rel = ?DEFAULT_REL_NAME,
+ rels = reltool_utils:default_rels(),
+ emu_name = ?DEFAULT_EMU_NAME,
+ profile = ?DEFAULT_PROFILE,
+ incl_sys_filters = reltool_utils:decode_regexps(incl_sys_filters, ?DEFAULT_INCL_SYS_FILTERS, []),
+ excl_sys_filters = reltool_utils:decode_regexps(excl_sys_filters, ?DEFAULT_EXCL_SYS_FILTERS, []),
+ incl_app_filters = reltool_utils:decode_regexps(incl_app_filters, ?DEFAULT_INCL_APP_FILTERS, []),
+ excl_app_filters = reltool_utils:decode_regexps(excl_app_filters, ?DEFAULT_EXCL_APP_FILTERS, []),
+ relocatable = ?DEFAULT_RELOCATABLE,
+ app_type = ?DEFAULT_APP_TYPE,
+ app_file = ?DEFAULT_APP_FILE,
+ incl_archive_filters = reltool_utils:decode_regexps(incl_archive_filters, ?DEFAULT_INCL_ARCHIVE_FILTERS, []),
+ excl_archive_filters = reltool_utils:decode_regexps(excl_archive_filters, ?DEFAULT_EXCL_ARCHIVE_FILTERS, []),
+ archive_opts = ?DEFAULT_ARCHIVE_OPTS,
+ debug_info = ?DEFAULT_DEBUG_INFO},
+ C2 = #common{sys_debug = [],
+ wx_debug = 0,
+ trap_exit = true,
+ app_tab = AppTab,
+ mod_tab = ModTab,
+ mod_used_by_tab = ModUsesTab},
+ S = #state{options = Opts},
+ parse_options(Opts, S, C2, Sys, {ok, []}).
+
+parse_options([{Key, Val} | KeyVals], S, C, Sys, Status) ->
+ case Key of
+ parent ->
+ parse_options(KeyVals, S#state{parent_pid = Val}, C, Sys, Status);
+ sys_debug ->
+ parse_options(KeyVals, S, C#common{sys_debug = Val}, Sys, Status);
+ wx_debug ->
+ parse_options(KeyVals, S, C#common{wx_debug = Val}, Sys, Status);
+ trap_exit ->
+ parse_options(KeyVals, S, C#common{trap_exit = Val}, Sys, Status);
+ config ->
+ {Sys2, Status2} = read_config(Sys, Val, Status),
+ parse_options(KeyVals, S, C, Sys2, Status2);
+ sys ->
+ {Sys2, Status2} = read_config(Sys, {sys, Val}, Status),
+ parse_options(KeyVals, S, C, Sys2, Status2);
+ _ ->
+ Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
+ Status2 = reltool_utils:return_first_error(Status, "Illegal option: " ++ Text),
+ parse_options(KeyVals, S, C, Sys, Status2)
+ end;
+parse_options([], S, C, Sys, Status) ->
+ {S#state{common = C, sys = Sys}, Status};
+parse_options(KeyVals, S, C, Sys, Status) ->
+ Text = lists:flatten(io_lib:format("~p", [KeyVals])),
+ Status2 = reltool_utils:return_first_error(Status, "Illegal options: " ++ Text),
+ {S#state{common = C, sys = Sys}, Status2}.
+
+loop(#state{common = C, sys = Sys} = S) ->
+ receive
+ {system, From, Msg} ->
+ sys:handle_system_msg(Msg, From, S#state.parent_pid, ?MODULE, C#common.sys_debug, S);
+ {call, ReplyTo, Ref, {get_config, InclDefaults, InclDerivates}} ->
+ Reply = do_get_config(S, InclDefaults, InclDerivates),
+ reltool_utils:reply(ReplyTo, Ref, Reply),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {load_config, SysConfig}} ->
+ {S2, Reply} = do_load_config(S, SysConfig),
+ reltool_utils:reply(ReplyTo, Ref, Reply),
+ ?MODULE:loop(S2);
+ {call, ReplyTo, Ref, {save_config, Filename, InclDefaults, InclDerivates}} ->
+ Reply = do_save_config(S, Filename, InclDefaults, InclDerivates),
+ reltool_utils:reply(ReplyTo, Ref, Reply),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, reset_config} ->
+ {S2, Status} = parse_options(S#state.options),
+ S3 = shrink_sys(S2),
+ {S4, Status2} = refresh(S3, true, Status),
+ {S5, Status3} = analyse(S4#state{old_sys = S4#state.sys}, Status2),
+ S6 =
+ case Status3 of
+ {ok, _Warnings} ->
+ S5#state{status = Status3, old_status = S#state.status};
+ {error, _} ->
+ S
+ end,
+ reltool_utils:reply(ReplyTo, Ref, Status3),
+ ?MODULE:loop(S6);
+ {call, ReplyTo, Ref, undo_config} ->
+ reltool_utils:reply(ReplyTo, Ref, ok),
+ S2 = S#state{sys = S#state.old_sys,
+ old_sys = S#state.sys,
+ status = S#state.old_status,
+ old_status = S#state.status},
+ ?MODULE:loop(S2);
+ {call, ReplyTo, Ref, {get_rel, RelName}} ->
+ Sys = S#state.sys,
+ Reply =
+ case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of
+ {value, Rel} ->
+ {ok, reltool_target:gen_rel(Rel, Sys)};
+ false ->
+ {error, "No such release"}
+ end,
+ reltool_utils:reply(ReplyTo, Ref, Reply),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {get_script, RelName}} ->
+ Sys = S#state.sys,
+ Reply =
+ case lists:keysearch(RelName, #rel.name, Sys#sys.rels) of
+ {value, Rel} ->
+ PathFlag = true,
+ Variables = [],
+ reltool_target:gen_script(Rel, Sys, PathFlag, Variables);
+ false ->
+ {error, "No such release"}
+ end,
+ reltool_utils:reply(ReplyTo, Ref, Reply),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {get_mod, ModName}} ->
+ Reply =
+ case ets:lookup(C#common.mod_tab, ModName) of
+ [M] ->
+ {ok, M};
+ [] ->
+ {ok, missing_mod(ModName, ?MISSING_APP)}
+ end,
+ reltool_utils:reply(ReplyTo, Ref, Reply),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {get_app, AppName}} when is_atom(AppName) ->
+ Reply =
+ case lists:keysearch(AppName, #app.name, Sys#sys.apps) of
+ {value, App} ->
+ {ok, App};
+ false ->
+ {error, enoent}
+ end,
+ reltool_utils:reply(ReplyTo, Ref, Reply),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {set_app, App}} ->
+ {S2, Status} = do_set_app(S, App, {ok, []}),
+ {S3, Status2} = analyse(S2, Status),
+ case Status2 of
+ {ok, Warnings} ->
+ App2 = ?KEYSEARCH(App#app.name,
+ #app.name,
+ (S3#state.sys)#sys.apps),
+ reltool_utils:reply(ReplyTo, Ref, {ok, App2, Warnings}),
+ ?MODULE:loop(S3);
+ {error, Reason} ->
+ reltool_utils:reply(ReplyTo, Ref, {error, Reason}),
+ ?MODULE:loop(S)
+ end;
+ {call, ReplyTo, Ref, {get_apps, Kind}} ->
+ AppNames =
+ case Kind of
+ whitelist ->
+ [A ||
+ A <- Sys#sys.apps,
+ A#app.is_pre_included =:= true];
+ blacklist ->
+ [A ||
+ A <- Sys#sys.apps,
+ A#app.is_pre_included =:= false];
+ source ->
+ [A ||
+ A <- Sys#sys.apps,
+ A#app.is_included =/= true,
+ A#app.is_pre_included =/= false];
+ derived ->
+ [A ||
+ A <- Sys#sys.apps,
+ A#app.is_included =:= true,
+ A#app.is_pre_included =/= true]
+ end,
+ reltool_utils:reply(ReplyTo, Ref, {ok, AppNames}),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {set_apps, Apps}} ->
+ {S2, Status} = lists:foldl(fun(A, {X, Y}) -> do_set_app(X, A, Y) end,
+ {S, {ok, []}},
+ Apps),
+ {S3, Status2} = analyse(S2, Status),
+ reltool_utils:reply(ReplyTo, Ref, Status2),
+ ?MODULE:loop(S3);
+ {call, ReplyTo, Ref, get_sys} ->
+ reltool_utils:reply(ReplyTo, Ref, {ok, Sys#sys{apps = undefined}}),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {set_sys, Sys2}} ->
+ S2 = S#state{sys = Sys2#sys{apps = Sys#sys.apps}},
+ Force =
+ (Sys2#sys.root_dir =/= Sys#sys.root_dir) orelse
+ (Sys2#sys.lib_dirs =/= Sys#sys.lib_dirs) orelse
+ (Sys2#sys.escripts =/= Sys#sys.escripts),
+ {S3, Status} = refresh(S2, Force, {ok, []}),
+ {S4, Status2} = analyse(S3#state{old_sys = S#state.sys}, Status),
+ S6 =
+ case Status2 of
+ {ok, _Warnings} ->
+ S4#state{status = Status2, old_status = S#state.status};
+ {error, _} ->
+ S
+ end,
+ reltool_utils:reply(ReplyTo, Ref, Status2),
+ ?MODULE:loop(S6);
+ {call, ReplyTo, Ref, get_status} ->
+ reltool_utils:reply(ReplyTo, Ref, S#state.status),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {gen_rel_files, Dir}} ->
+ Status =
+ case reltool_target:gen_rel_files(S#state.sys, Dir) of
+ ok ->
+ {ok, []};
+ {error, Reason} ->
+ {error, Reason}
+ end,
+ reltool_utils:reply(ReplyTo, Ref, Status),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {gen_target, Dir}} ->
+ Reply = reltool_target:gen_target(S#state.sys, Dir),
+ reltool_utils:reply(ReplyTo, Ref, Reply),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, gen_spec} ->
+ Reply = reltool_target:gen_spec(S#state.sys),
+ reltool_utils:reply(ReplyTo, Ref, Reply),
+ ?MODULE:loop(S);
+ {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid ->
+ exit(Reason);
+ {call, ReplyTo, Ref, Msg} when is_pid(ReplyTo), is_reference(Ref) ->
+ error_logger:format("~p~p got unexpected call:\n\t~p\n",
+ [?MODULE, self(), Msg]),
+ reltool_utils:reply(ReplyTo, Ref, {error, {invalid_call, Msg}}),
+ ?MODULE:loop(S);
+ Msg ->
+ error_logger:format("~p~p got unexpected message:\n\t~p\n",
+ [?MODULE, self(), Msg]),
+ ?MODULE:loop(S)
+ end.
+
+ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+do_set_app(#state{sys = Sys} = S, App, Status) ->
+ AppName = App#app.name,
+ {App2, Status2} = refresh_app(App, false, Status),
+ Apps = Sys#sys.apps,
+ Apps2 = lists:keystore(AppName, #app.name, Apps, App2),
+ Escripts = [A#app.active_dir || A <- Apps2, A#app.is_escript],
+ Sys2 = Sys#sys{apps = Apps2, escripts = Escripts},
+ {S#state{sys = Sys2}, Status2}.
+
+analyse(#state{common = C, sys = #sys{apps = Apps0} = Sys} = S, Status) ->
+ Apps = lists:keydelete(?MISSING_APP, #app.name, Apps0),
+ ets:delete_all_objects(C#common.app_tab),
+ ets:delete_all_objects(C#common.mod_tab),
+ ets:delete_all_objects(C#common.mod_used_by_tab),
+ MissingApp = default_app(?MISSING_APP, "missing"),
+ ets:insert(C#common.app_tab, MissingApp),
+
+ Apps2 = lists:map(fun(App) -> app_init_is_included(C, Sys, App) end, Apps),
+ Apps3 =
+ case app_propagate_is_included(C, Sys, Apps2, []) of
+ [] ->
+ Apps2;
+ MissingMods ->
+ %% io:format("Missing mods: ~p\n", [MissingMods]),
+ MissingApp2 = MissingApp#app{label = ?MISSING_APP_TEXT,
+ info = missing_app_info(""),
+ mods = MissingMods,
+ status = missing,
+ uses_mods = []},
+ [MissingApp2 | Apps2]
+ end,
+ app_propagate_is_used_by(C, Apps3),
+ Apps4 = read_apps(C, Sys, Apps3, []),
+ %% io:format("Missing app: ~p\n", [lists:keysearch(?MISSING_APP, #app.name, Apps4)]),
+ Sys2 = Sys#sys{apps = Apps4},
+ try
+ Status2 = verify_config(Sys2, Status),
+ {S#state{sys = Sys2}, Status2}
+ catch
+ throw:{error, Status3} ->
+ {S, Status3}
+ end.
+
+app_init_is_included(C, Sys, #app{mods = Mods} = A) ->
+ AppCond =
+ case A#app.incl_cond of
+ undefined -> Sys#sys.incl_cond;
+ _ -> A#app.incl_cond
+ end,
+ ModCond =
+ case A#app.mod_cond of
+ undefined -> Sys#sys.mod_cond;
+ _ -> A#app.mod_cond
+ end,
+ IsIncl =
+ case AppCond of
+ include -> true;
+ exclude -> false;
+ derived -> undefined
+ end,
+ A2 = A#app{is_pre_included = IsIncl, is_included = IsIncl},
+ ets:insert(C#common.app_tab, A2),
+ lists:foreach(fun(Mod) -> mod_init_is_included(C, Mod, ModCond, AppCond, undefined) end, Mods),
+ %%app_mod_init_is_included(C, AppName, Info, ModCond, AppCond),
+ A2.
+
+mod_init_is_included(C, M, ModCond, AppCond, Default) ->
+ %% print(M#mod.name, hipe, "incl_cond -> ~p\n", [AppCond]),
+ IsIncl =
+ case AppCond of
+ include ->
+ case M#mod.incl_cond of
+ include ->
+ true;
+ exclude ->
+ false;
+ undefined ->
+ %% print(M#mod.name, hipe, "mod_cond -> ~p\n", [ModCond]),
+ case ModCond of
+ all -> true;
+ app -> false_to_undefined(M#mod.is_app_mod);
+ ebin -> false_to_undefined(M#mod.is_ebin_mod);
+ derived -> Default;
+ none -> false
+ end
+ end;
+ exclude ->
+ false;
+ derived ->
+ case M#mod.incl_cond of
+ include ->
+ true;
+ exclude ->
+ false;
+ undefined ->
+ Default
+ end
+ end,
+ M2 = M#mod{is_pre_included = IsIncl, is_included = IsIncl},
+ %% print(M#mod.name, hipe, "~p -> ~p\n", [M2, IsIncl]),
+ ets:insert(C#common.mod_tab, M2).
+
+false_to_undefined(Bool) ->
+ case Bool of
+ false -> undefined;
+ _ -> Bool
+ end.
+
+app_propagate_is_included(C, Sys, [#app{mods = Mods} = A | Apps], Acc) ->
+ Acc2 = mod_propagate_is_included(C, Sys, A, Mods, Acc),
+ app_propagate_is_included(C, Sys, Apps, Acc2);
+app_propagate_is_included(_C, _Sys, [], Acc) ->
+ Acc.
+
+mod_propagate_is_included(C, Sys, A, [#mod{name = ModName} | Mods], Acc) ->
+ [M2] = ets:lookup(C#common.mod_tab, ModName),
+ %% print(ModName, file, "Maybe Prop ~p -> ~p\n", [M2, M2#mod.is_included]),
+ %% print(ModName, filename, "Maybe Prop ~p -> ~p\n", [M2, M2#mod.is_included]),
+ Acc2 =
+ case M2#mod.is_included of
+ true ->
+ %% Propagate include mark
+ mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, Acc);
+ false ->
+ Acc;
+ undefined ->
+ Acc
+ end,
+ mod_propagate_is_included(C, Sys, A, Mods, Acc2);
+mod_propagate_is_included(_C, _Sys, _A, [], Acc) ->
+ Acc.
+
+mod_mark_is_included(C, Sys, UsedByName, [ModName | ModNames], Acc) ->
+ Acc3 =
+ case ets:lookup(C#common.mod_tab, ModName) of
+ [M] ->
+ %% print(UsedByName, file, "Maybe Mark ~p -> ~p\n", [M, M#mod.is_included]),
+ %% print(UsedByName, filename, "Maybe Mark ~p -> ~p\n", [M, M#mod.is_included]),
+ case M#mod.is_included of
+ true ->
+ %% Already marked
+ Acc;
+ false ->
+ %% Already marked
+ Acc;
+ undefined ->
+ %% Mark and propagate
+ M2 =
+ case M#mod.incl_cond of
+ include ->
+ M#mod{is_pre_included = true, is_included = true};
+ exclude ->
+ M#mod{is_pre_included = true, is_included = true};
+ undefined ->
+ M#mod{is_included = true}
+ end,
+ ets:insert(C#common.mod_tab, M2),
+ %% io:format("Propagate mod: ~p -> ~p (~p)\n", [UsedByName, ModName, M#mod.incl_cond]),
+ [A] = ets:lookup(C#common.app_tab, M2#mod.app_name),
+ Acc2 =
+ case A#app.is_included of
+ true ->
+ Acc;
+ false ->
+ Acc;
+ undefined ->
+ ModCond =
+ case A#app.mod_cond of
+ undefined -> Sys#sys.mod_cond;
+ _ -> A#app.mod_cond
+ end,
+ Filter =
+ fun(M3) ->
+ case ModCond of
+ all -> true;
+ app -> M3#mod.is_app_mod;
+ ebin -> M3#mod.is_ebin_mod;
+ derived -> false;
+ none -> false
+ end
+ end,
+ Mods = lists:filter(Filter, A#app.mods),
+ %% io:format("Propagate app: ~p ~p -> ~p\n",
+ %% [UsedByName, A#app.name, [M3#mod.name || M3 <- Mods]]),
+ A2 = A#app{is_included = true},
+ ets:insert(C#common.app_tab, A2),
+ mod_mark_is_included(C, Sys, ModName, [M3#mod.name || M3 <- Mods], Acc)
+ end,
+ mod_mark_is_included(C, Sys, ModName, M2#mod.uses_mods, Acc2)
+ end;
+ [] ->
+ M = missing_mod(ModName, ?MISSING_APP),
+ M2 = M#mod{is_included = true},
+ ets:insert(C#common.mod_tab, M2),
+ ets:insert(C#common.mod_used_by_tab, {UsedByName, ModName}),
+ [M2 | Acc]
+ end,
+ mod_mark_is_included(C, Sys, UsedByName, ModNames, Acc3);
+mod_mark_is_included(_C, _Sys, _UsedByName, [], Acc) ->
+ Acc.
+
+app_propagate_is_used_by(C, [#app{mods = Mods, name = Name} | Apps]) ->
+ case Name =:= ?MISSING_APP of
+ true -> ok;
+ false -> ok
+ end,
+ mod_propagate_is_used_by(C, Mods),
+ app_propagate_is_used_by(C, Apps);
+app_propagate_is_used_by(_C, []) ->
+ ok.
+
+mod_propagate_is_used_by(C, [#mod{name = ModName} | Mods]) ->
+ [M] = ets:lookup(C#common.mod_tab, ModName),
+ case M#mod.is_included of
+ true ->
+ [ets:insert(C#common.mod_used_by_tab, {UsedModName, ModName}) ||
+ UsedModName <- M#mod.uses_mods];
+ false ->
+ ignore;
+ undefined ->
+ ignore
+ end,
+ mod_propagate_is_used_by(C, Mods);
+mod_propagate_is_used_by(_C, []) ->
+ ok.
+
+read_apps(C, Sys, [#app{mods = Mods, is_included = IsIncl} = A | Apps], Acc) ->
+ {Mods2, IsIncl2} = read_apps(C, Sys, A, Mods, [], IsIncl),
+ %% reltool_utils:print(A#app.name, stdlib, "Mods2: ~p\n", [[M#mod.status || M <- Mods2]]),
+ Status =
+ case lists:keysearch(missing, #mod.status, Mods2) of
+ {value, _} -> missing;
+ false -> ok
+ end,
+ UsesMods = [M#mod.uses_mods || M <- Mods2, M#mod.is_included =:= true],
+ UsesMods2 = lists:usort(lists:flatten(UsesMods)),
+ UsesApps = [M#mod.app_name || ModName <- UsesMods2, M <- ets:lookup(C#common.mod_tab, ModName)],
+ UsesApps2 = lists:usort(UsesApps),
+ UsedByMods = [M#mod.used_by_mods || M <- Mods2, M#mod.is_included =:= true],
+ UsedByMods2 = lists:usort(lists:flatten(UsedByMods)),
+ UsedByApps = [M#mod.app_name || ModName <- UsedByMods2, M <- ets:lookup(C#common.mod_tab, ModName)],
+ UsedByApps2 = lists:usort(UsedByApps),
+
+ A2 = A#app{mods = Mods2,
+ status = Status,
+ uses_mods = UsesMods2,
+ used_by_mods = UsedByMods2,
+ uses_apps = UsesApps2,
+ used_by_apps = UsedByApps2,
+ is_included = IsIncl2},
+ read_apps(C, Sys, Apps, [A2 | Acc]);
+read_apps(_C, _Sys, [], Acc) ->
+ lists:reverse(Acc).
+
+read_apps(C, Sys, A, [#mod{name = ModName} | Mods], Acc, IsIncl) ->
+ [M2] = ets:lookup(C#common.mod_tab, ModName),
+ Status = do_get_status(M2),
+ %% print(M2#mod.name, hipe, "status -> ~p\n", [Status]),
+ {IsIncl2, M3} =
+ case M2#mod.is_included of
+ true ->
+ UsedByMods = [N || {_, N} <- ets:lookup(C#common.mod_used_by_tab, ModName)],
+ {true, M2#mod{status = Status, used_by_mods = UsedByMods}};
+ _ ->
+ {IsIncl, M2#mod{status = Status, used_by_mods = []}}
+ end,
+ ets:insert(C#common.mod_tab, M3),
+ read_apps(C, Sys, A, Mods, [M3 | Acc], IsIncl2);
+read_apps(_C, _Sys, _A, [], Acc, IsIncl) ->
+ {lists:reverse(Acc), IsIncl}.
+
+do_get_status(M) ->
+ if
+ M#mod.exists =:= false, M#mod.is_included =/= false ->
+ missing;
+ true ->
+ ok
+ end.
+
+shrink_sys(#state{sys = #sys{apps = Apps} = Sys} = S) ->
+ Apps2 = lists:zf(fun filter_app/1, Apps),
+ S#state{sys = Sys#sys{apps = Apps2}}.
+
+filter_app(A) ->
+ Mods = [M#mod{is_app_mod = undefined,
+ is_ebin_mod = undefined,
+ uses_mods = undefined,
+ exists = false,
+ is_pre_included = undefined,
+ is_included = undefined} ||
+ M <- A#app.mods,
+ M#mod.incl_cond =/= undefined],
+ if
+ A#app.is_escript ->
+ {true, A#app{vsn = undefined,
+ label = undefined,
+ info = undefined,
+ mods = [],
+ uses_mods = undefined,
+ is_included = undefined}};
+ Mods =:= [],
+ A#app.mod_cond =:= undefined,
+ A#app.incl_cond =:= undefined,
+ A#app.use_selected_vsn =:= undefined ->
+ false;
+ true ->
+ {Dir, Dirs} =
+ case A#app.use_selected_vsn of
+ undefined ->
+ {shrinked, []};
+ false ->
+ {shrinked, []};
+ true ->
+ {A#app.active_dir, [A#app.active_dir]};
+ _ when A#app.is_escript ->
+ {A#app.active_dir, [A#app.active_dir]}
+ end,
+ OptVsn =
+ case A#app.use_selected_vsn of
+ undefined -> undefined;
+ false -> undefined;
+ true -> A#app.vsn
+ end,
+ {true, A#app{active_dir = Dir,
+ sorted_dirs = Dirs,
+ vsn = OptVsn,
+ label = undefined,
+ info = undefined,
+ mods = Mods,
+ uses_mods = undefined,
+ is_included = undefined}}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+refresh_app(#app{name = AppName,
+ is_escript = IsEscript,
+ active_dir = ActiveDir,
+ label = OptLabel,
+ mods = Mods} = App,
+ Force,
+ Status) ->
+ if
+ Force; OptLabel =:= undefined ->
+ {AppInfo, EbinMods, Status3} =
+ case IsEscript of
+ false ->
+
+ %% Add info from .app file
+ Base = get_base(AppName, ActiveDir),
+ {_, DefaultVsn} = reltool_utils:split_app_name(Base),
+ Ebin = filename:join([ActiveDir, "ebin"]),
+ AppFile = filename:join([Ebin, atom_to_list(AppName) ++ ".app"]),
+ {AI, Status2} = read_app_info(AppFile, AppFile, AppName, DefaultVsn, Status),
+ {AI, read_ebin_mods(Ebin, AppName), Status2};
+ true ->
+ {App#app.info, Mods, Status}
+ end,
+
+ %% Add non-existing modules
+ AppModNames =
+ case AppInfo#app_info.mod of
+ {StartModName, _} ->
+ case lists:member(StartModName, AppInfo#app_info.modules) of
+ true -> AppInfo#app_info.modules;
+ false -> [StartModName | AppInfo#app_info.modules]
+ end;
+ undefined ->
+ AppInfo#app_info.modules
+ end,
+ MissingMods = add_missing_mods(AppName, EbinMods, AppModNames),
+
+ %% Add optional user config for each module
+ Mods2 = add_mod_config(MissingMods ++ EbinMods, Mods),
+
+ %% Set app flag for each module in app file
+ Mods3 = set_mod_flags(Mods2, AppModNames),
+ AppVsn = AppInfo#app_info.vsn,
+ AppLabel =
+ case AppVsn of
+ "" -> atom_to_list(AppName);
+ _ -> atom_to_list(AppName) ++ "-" ++ AppVsn
+ end,
+ App2 = App#app{vsn = AppVsn,
+ label = AppLabel,
+ info = AppInfo,
+ mods = lists:keysort(#mod.name, Mods3)},
+ {App2, Status3};
+ true ->
+ {App, Status}
+ end.
+
+missing_app_info(Vsn) ->
+ #app_info{vsn = Vsn}.
+
+read_app_info(_AppFileOrBin, _AppFile, erts, DefaultVsn, Status) ->
+ {missing_app_info(DefaultVsn), Status};
+read_app_info(AppFileOrBin, AppFile, AppName, DefaultVsn, Status) ->
+ EnoentText = file:format_error(enoent),
+ case reltool_utils:prim_consult(AppFileOrBin) of
+ {ok, [{application, AppName, Info}]} ->
+ AI = #app_info{vsn = DefaultVsn},
+ parse_app_info(AppFile, Info, AI, Status);
+ {ok, _BadApp} ->
+ Text = lists:concat([AppName, ": Illegal contents in app file ", AppFile]),
+ {missing_app_info(DefaultVsn), reltool_utils:add_warning(Status, Text)};
+ {error, Text} when Text =:= EnoentText->
+ {missing_app_info(DefaultVsn), Status};
+ {error, Text} ->
+ Text2 = lists:concat([AppName, ": Cannot parse app file ", AppFile, " (", Text, ")."]),
+ {missing_app_info(DefaultVsn), reltool_utils:add_warning(Status, Text2)}
+ end.
+
+parse_app_info(File, [{Key, Val} | KeyVals], AI, Status) ->
+ case Key of
+ description -> parse_app_info(File, KeyVals, AI#app_info{description = Val}, Status);
+ id -> parse_app_info(File, KeyVals, AI#app_info{id = Val}, Status);
+ vsn -> parse_app_info(File, KeyVals, AI#app_info{vsn = Val}, Status);
+ modules -> parse_app_info(File, KeyVals, AI#app_info{modules = Val}, Status);
+ maxP -> parse_app_info(File, KeyVals, AI#app_info{maxP = Val}, Status);
+ maxT -> parse_app_info(File, KeyVals, AI#app_info{maxT = Val}, Status);
+ registered -> parse_app_info(File, KeyVals, AI#app_info{registered = Val}, Status);
+ included_applications -> parse_app_info(File, KeyVals, AI#app_info{incl_apps = Val}, Status);
+ applications -> parse_app_info(File, KeyVals, AI#app_info{applications = Val}, Status);
+ env -> parse_app_info(File, KeyVals, AI#app_info{env = Val}, Status);
+ mod -> parse_app_info(File, KeyVals, AI#app_info{mod = Val}, Status);
+ start_phases -> parse_app_info(File, KeyVals, AI#app_info{start_phases = Val}, Status);
+ _ -> parse_app_info(File, KeyVals, AI, reltool_utils:add_warning(Status, lists:concat(["Unexpected item ", Key, "in app file ", File])))
+ end;
+parse_app_info(_, [], AI, Status) ->
+ {AI, Status}.
+
+read_ebin_mods(Ebin, AppName) ->
+ case erl_prim_loader:list_dir(Ebin) of
+ {ok, Files} ->
+ Ext = code:objfile_extension(),
+ InitMod = fun(F) ->
+ File = filename:join([Ebin, F]),
+ init_mod(AppName, File, File, Ext)
+ end,
+ Files2 = [F || F <- Files, filename:extension(F) =:= Ext],
+ pmap(InitMod, Files2);
+ error ->
+ []
+ end.
+
+pmap(Fun, List) ->
+ lists:map(Fun, List).
+ %% N = erlang:system_info(schedulers) * 2,
+ %% pmap(Fun, List, 0, N, 0, [], []).
+
+%% -record(pmap_res, {count, ref, res}).
+%% -record(pmap_wait, {count, ref, pid}).
+%%
+%% pmap(Fun, [H | T], N, Max, Count, WaitFor, Results) when N < Max ->
+%% Ref = make_ref(),
+%% Parent = self(),
+%% Count2 = Count + 1,
+%% Pid = spawn_link(fun() -> Parent ! #pmap_res{count = Count2, ref = Ref, res = Fun(H)}, unlink(Parent) end),
+%% PW = #pmap_wait{count = Count2, pid = Pid, ref = Ref},
+%% pmap(Fun, T, N + 1, Max, Count2, [PW | WaitFor], Results);
+%% pmap(_Fun, [], _N, _Max, _Count, [], Results) ->
+%% %% Sort results and return them in the same orderas the original list
+%% [PR#pmap_res.res || PR <- lists:keysort(#pmap_res.count, Results)];
+%% pmap(Fun, List, N, Max, Count, WaitFor, Results) ->
+%% receive
+%% #pmap_res{ref = Ref} = PR ->
+%% WaitFor2 = lists:keydelete(Ref, #pmap_wait.ref, WaitFor),
+%% pmap(Fun, List, N - 1, Max, Count, WaitFor2, [PR | Results]);
+%% {'EXIT', Reason} ->
+%% exit(Reason)
+%% end.
+
+init_mod(AppName, File, FileOrBin, Ext) ->
+ UsesMods = xref_mod(FileOrBin),
+ Base = filename:basename(File, Ext),
+ ModName = list_to_atom(Base),
+ #mod{name = ModName,
+ app_name = AppName,
+ incl_cond = undefined,
+ is_ebin_mod = true,
+ uses_mods = UsesMods,
+ exists = true}.
+
+xref_mod({Base, Bin}) when is_binary(Bin) ->
+ Dir = filename:absname("reltool_server.tmp"),
+ ok = reltool_utils:recursive_delete(Dir),
+ ok = file:make_dir(Dir),
+ File = filename:join([Dir, Base]),
+ ok = file:write_file(File, Bin),
+ Res = xref_mod(File),
+ ok = reltool_utils:recursive_delete(Dir),
+ Res;
+xref_mod(File) when is_list(File) ->
+ {ok, Pid} = xref:start([{xref_mode, modules}]),
+ link(Pid),
+ ok = xref:set_default(Pid, [{verbose,false}, {warnings, false}]),
+ ok = xref:set_library_path(Pid, []),
+ {ok, _} = xref:add_module(Pid, File, []),
+ {ok, UnknownMods} = xref:q(Pid, "UM", []),
+ %% {ok, ExportedFuns} = xref:q(Pid, "X", []),
+ %% io:format("Unres: ~p\n", [xref:variables(Pid, [predefined])]),
+ %% io:format("Q: ~p\n", [xref:q(Pid, "XU", [])]),
+ Ref = erlang:monitor(process, Pid),
+ unlink(Pid),
+ xref:stop(Pid),
+ wait_for_processto_die(Ref, Pid, File),
+ UnknownMods.
+
+wait_for_processto_die(Ref, Pid, File) ->
+ receive
+ {'DOWN', Ref, _Type, _Object, _Info} ->
+ ok
+ after timer:seconds(30) ->
+ error_logger:error_msg("~p(~p): Waiting for process ~p to die ~p\n",
+ [?MODULE, ?LINE, Pid, File]),
+ wait_for_processto_die(Ref, Pid, File)
+ end.
+
+add_missing_mods(AppName, EbinMods, AppModNames) ->
+ EbinModNames = [M#mod.name || M <- EbinMods],
+ MissingModNames = AppModNames -- EbinModNames,
+ [missing_mod(ModName, AppName) || ModName <- MissingModNames].
+
+missing_mod(ModName, AppName) ->
+ %% io:format("Missing: ~p -> ~p\n", [AppName, ModName]),
+ #mod{name = ModName,
+ app_name = AppName,
+ incl_cond = undefined,
+ is_ebin_mod = false,
+ exists = false,
+ status = missing,
+ uses_mods = []}.
+
+add_mod_config(Mods, ModConfigs) ->
+ AddConfig =
+ fun(Config, Acc) ->
+ case lists:keysearch(Config#mod.name, #mod.name, Mods) of
+ {value, M} ->
+ M2 = M#mod{incl_cond = Config#mod.incl_cond},
+ lists:keystore(Config#mod.name, #mod.name, Acc, M2);
+ false ->
+ Config2 = Config#mod{uses_mods = [], exists = false},
+ [Config2 | Acc]
+ end
+ end,
+ lists:foldl(AddConfig, Mods, ModConfigs).
+
+set_mod_flags(Mods, AppModNames) ->
+ SetFlags =
+ fun(#mod{name = N} = M) ->
+ M#mod{is_app_mod = lists:member(N, AppModNames)}
+ end,
+ lists:map(SetFlags, Mods).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+do_get_config(S, InclDefaults, InclDerivates) ->
+ S2 =
+ case InclDerivates of
+ false -> shrink_sys(S);
+ true -> S
+ end,
+ {ok, reltool_target:gen_config(S2#state.sys, InclDefaults)}.
+
+do_save_config(S, Filename, InclDefaults, InclDerivates) ->
+ {ok, Config} = do_get_config(S, InclDefaults, InclDerivates),
+ IoList = io_lib:format("%% config generated at ~w ~w\n~p.\n\n",
+ [date(), time(), Config]),
+ file:write_file(Filename, IoList).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+do_load_config(S, SysConfig) ->
+ OldSys = S#state.sys,
+ S2 = shrink_sys(S),
+ ShrinkedSys = S2#state.sys,
+ {NewSys, Status} = read_config(ShrinkedSys#sys{apps = []}, SysConfig, {ok, []}),
+ case Status of
+ {ok, _Warnings} ->
+ Force = false,
+ {MergedSys, Status2} = merge_config(OldSys, NewSys, Force, Status),
+ {S3, Status3} = analyse(S2#state{sys = MergedSys, old_sys = OldSys}, Status2),
+ S4 =
+ case Status3 of
+ {ok, _Warnings2} ->
+ S3#state{status = Status3, old_status = S#state.status};
+ {error, _} ->
+ S
+ end,
+ {S4, Status3};
+ {error, _} ->
+ %% Keep old state
+ {S, Status}
+ end.
+
+read_config(OldSys, Filename, Status) when is_list(Filename) ->
+ case file:consult(Filename) of
+ {ok, [SysConfig | _]} ->
+ read_config(OldSys, SysConfig, Status);
+ {ok, Content} ->
+ Text = lists:flatten(io_lib:format("~p", [Content])),
+ {OldSys, reltool_utils:return_first_error(Status, "Illegal file content: " ++ Text)};
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ {OldSys, reltool_utils:return_first_error(Status, "File access: " ++ Text)}
+ end;
+read_config(OldSys, {sys, KeyVals}, Status) ->
+ {NewSys, Status2} =
+ try
+ decode(OldSys#sys{apps = [], rels = []}, KeyVals, Status)
+ catch
+ throw:{error, Text} ->
+ {OldSys, reltool_utils:return_first_error(Status, Text)}
+ end,
+ Apps = [A#app{mods = lists:sort(A#app.mods)} || A <- NewSys#sys.apps],
+ case NewSys#sys.rels of
+ [] -> Rels = reltool_utils:default_rels();
+ Rels -> ok
+ end,
+ NewSys2 = NewSys#sys{apps = lists:sort(Apps), rels = lists:sort(Rels)},
+ case lists:keysearch(NewSys2#sys.boot_rel, #rel.name, NewSys2#sys.rels) of
+ {value, _} ->
+ {NewSys2, Status2};
+ false ->
+ Text2 = "Missing rel: " ++ NewSys2#sys.boot_rel,
+ {OldSys, reltool_utils:return_first_error(Status2, Text2)}
+ end;
+read_config(OldSys, BadConfig, Status) ->
+ Text = lists:flatten(io_lib:format("~p", [BadConfig])),
+ {OldSys, reltool_utils:return_first_error(Status, "Illegal content: " ++ Text)}.
+
+decode(#sys{apps = Apps} = Sys, [{erts = Name, AppKeyVals} | SysKeyVals], Status)
+ when is_atom(Name), is_list(AppKeyVals) ->
+ App = default_app(Name),
+ {App2, Status2} = decode(App, AppKeyVals, Status),
+ decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals, Status2);
+decode(#sys{apps = Apps} = Sys, [{app, Name, AppKeyVals} | SysKeyVals], Status)
+ when is_atom(Name), is_list(AppKeyVals) ->
+ App = default_app(Name),
+ {App2, Status2} = decode(App, AppKeyVals, Status),
+ decode(Sys#sys{apps = [App2 | Apps]}, SysKeyVals, Status2);
+decode(#sys{apps = Apps, escripts = Escripts} = Sys, [{escript, File, AppKeyVals} | SysKeyVals], Status)
+ when is_list(File), is_list(AppKeyVals) ->
+ {Name, Label} = split_escript_name(File),
+ App = default_app(Name, File),
+ App2 = App#app{is_escript = true,
+ label = Label,
+ info = missing_app_info(""),
+ active_dir = File,
+ sorted_dirs = [File]},
+ {App3, Status2} = decode(App2, AppKeyVals, Status),
+ decode(Sys#sys{apps = [App3 | Apps], escripts = [File | Escripts]}, SysKeyVals, Status2);
+decode(#sys{rels = Rels} = Sys, [{rel, Name, Vsn, RelApps} | SysKeyVals], Status)
+ when is_list(Name), is_list(Vsn), is_list(RelApps) ->
+ Rel = #rel{name = Name, vsn = Vsn, rel_apps = []},
+ {Rel2, Status2} = decode(Rel, RelApps, Status),
+ decode(Sys#sys{rels = [Rel2 | Rels]}, SysKeyVals, Status2);
+decode(#sys{} = Sys, [{Key, Val} | KeyVals], Status) ->
+ {Sys3, Status3} =
+ case Key of
+ root_dir when is_list(Val) ->
+ {Sys#sys{root_dir = Val}, Status};
+ lib_dirs when is_list(Val) ->
+ {Sys#sys{lib_dirs = Val}, Status};
+ mod_cond when Val =:= all; Val =:= app;
+ Val =:= ebin; Val =:= derived;
+ Val =:= none ->
+ {Sys#sys{mod_cond = Val}, Status};
+ incl_cond when Val =:= include; Val =:= exclude;
+ Val =:= derived ->
+ {Sys#sys{incl_cond = Val}, Status};
+ boot_rel when is_list(Val) ->
+ {Sys#sys{boot_rel = Val}, Status};
+ emu_name when is_list(Val) ->
+ {Sys#sys{emu_name = Val}, Status};
+ profile when Val =:= development ->
+ Val = ?DEFAULT_PROFILE, % assert,
+ {Sys#sys{profile = Val,
+ incl_sys_filters = reltool_utils:decode_regexps(incl_sys_filters,
+ ?DEFAULT_INCL_SYS_FILTERS,
+ Sys#sys.incl_sys_filters),
+ excl_sys_filters = reltool_utils:decode_regexps(excl_sys_filters,
+ ?DEFAULT_EXCL_SYS_FILTERS,
+ Sys#sys.excl_sys_filters),
+ incl_app_filters = reltool_utils:decode_regexps(incl_app_filters,
+ ?DEFAULT_INCL_APP_FILTERS,
+ Sys#sys.incl_app_filters),
+ excl_app_filters = reltool_utils:decode_regexps(excl_app_filters,
+ ?DEFAULT_EXCL_APP_FILTERS,
+ Sys#sys.excl_app_filters)},
+ Status};
+ profile when Val =:= embedded ->
+ {Sys#sys{profile = Val,
+ incl_sys_filters = reltool_utils:decode_regexps(incl_sys_filters,
+ ?EMBEDDED_INCL_SYS_FILTERS,
+ Sys#sys.incl_sys_filters),
+ excl_sys_filters = reltool_utils:decode_regexps(excl_sys_filters,
+ ?EMBEDDED_EXCL_SYS_FILTERS,
+ Sys#sys.excl_sys_filters),
+ incl_app_filters = reltool_utils:decode_regexps(incl_app_filters,
+ ?EMBEDDED_INCL_APP_FILTERS,
+ Sys#sys.incl_app_filters),
+ excl_app_filters = reltool_utils:decode_regexps(excl_app_filters,
+ ?EMBEDDED_EXCL_APP_FILTERS,
+ Sys#sys.excl_app_filters)},
+ Status};
+ profile when Val =:= standalone ->
+ {Sys#sys{profile = Val,
+ incl_sys_filters = reltool_utils:decode_regexps(incl_sys_filters,
+ ?STANDALONE_INCL_SYS_FILTERS,
+ Sys#sys.incl_sys_filters),
+ excl_sys_filters = reltool_utils:decode_regexps(excl_sys_filters,
+ ?STANDALONE_EXCL_SYS_FILTERS,
+ Sys#sys.excl_sys_filters),
+ incl_app_filters = reltool_utils:decode_regexps(incl_app_filters,
+ ?STANDALONE_INCL_APP_FILTERS,
+ Sys#sys.incl_app_filters),
+ excl_app_filters = reltool_utils:decode_regexps(excl_app_filters,
+ ?STANDALONE_EXCL_APP_FILTERS,
+ Sys#sys.excl_app_filters)},
+ Status};
+ incl_sys_filters ->
+ {Sys#sys{incl_sys_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.incl_sys_filters)}, Status};
+ excl_sys_filters ->
+ {Sys#sys{excl_sys_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.excl_sys_filters)}, Status};
+ incl_app_filters ->
+ {Sys#sys{incl_app_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.incl_app_filters)}, Status};
+ excl_app_filters ->
+ {Sys#sys{excl_app_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.excl_app_filters)}, Status};
+ incl_archive_filters ->
+ {Sys#sys{incl_archive_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.incl_archive_filters)}, Status};
+ excl_archive_filters ->
+ {Sys#sys{excl_archive_filters = reltool_utils:decode_regexps(Key, Val, Sys#sys.excl_archive_filters)}, Status};
+ archive_opts when is_list(Val) ->
+ {Sys#sys{archive_opts = Val}, Status};
+ relocatable when Val =:= true; Val =:= false ->
+ {Sys#sys{relocatable = Val}, Status};
+ app_type when Val =:= permanent; Val =:= transient; Val =:= temporary;
+ Val =:= load; Val =:= none ->
+ {Sys#sys{app_type = Val}, Status};
+ app_file when Val =:= keep; Val =:= strip, Val =:= all ->
+ {Sys#sys{app_file = Val}, Status};
+ debug_info when Val =:= keep; Val =:= strip ->
+ {Sys#sys{debug_info = Val}, Status};
+ _ ->
+ Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
+ {Sys, reltool_utils:return_first_error(Status, "Illegal option: " ++ Text)}
+ end,
+ decode(Sys3, KeyVals, Status3);
+decode(#app{} = App, [{Key, Val} | KeyVals], Status) ->
+ {App2, Status2} =
+ case Key of
+ mod_cond when Val =:= all; Val =:= app; Val =:= ebin; Val =:= derived; Val =:= none ->
+ {App#app{mod_cond = Val}, Status};
+ incl_cond when Val =:= include; Val =:= exclude; Val =:= derived ->
+ {App#app{incl_cond = Val}, Status};
+
+ debug_info when Val =:= keep; Val =:= strip ->
+ {App#app{debug_info = Val}, Status};
+ app_file when Val =:= keep; Val =:= strip, Val =:= all ->
+ {App#app{app_file = Val}, Status};
+ app_type when Val =:= permanent; Val =:= transient; Val =:= temporary;
+ Val =:= load; Val =:= none ->
+ {App#app{app_type = Val}, Status};
+ incl_app_filters ->
+ {App#app{incl_app_filters = reltool_utils:decode_regexps(Key, Val, App#app.incl_app_filters)}, Status};
+ excl_app_filters ->
+ {App#app{excl_app_filters = reltool_utils:decode_regexps(Key, Val, App#app.excl_app_filters)}, Status};
+ incl_archive_filters ->
+ {App#app{incl_archive_filters = reltool_utils:decode_regexps(Key, Val, App#app.incl_archive_filters)}, Status};
+ excl_archive_filters ->
+ {App#app{excl_archive_filters = reltool_utils:decode_regexps(Key, Val, App#app.excl_archive_filters)}, Status};
+ archive_opts when is_list(Val) ->
+ {App#app{archive_opts = Val}, Status};
+ vsn when is_list(Val) ->
+ {App#app{use_selected_vsn = true, vsn = Val}, Status};
+ _ ->
+ Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
+ {App, reltool_utils:return_first_error(Status, "Illegal option: " ++ Text)}
+ end,
+ decode(App2, KeyVals, Status2);
+decode(#app{mods = Mods} = App, [{mod, Name, ModKeyVals} | AppKeyVals], Status) ->
+ {Mod, Status2} = decode(#mod{name = Name}, ModKeyVals, Status),
+ decode(App#app{mods = [Mod | Mods]}, AppKeyVals, Status2);
+decode(#mod{} = Mod, [{Key, Val} | KeyVals], Status) ->
+ {Mod2, Status2} =
+ case Key of
+ incl_cond when Val =:= include; Val =:= exclude; Val =:= derived ->
+ {Mod#mod{incl_cond = Val}, Status};
+ debug_info when Val =:= keep; Val =:= strip ->
+ {Mod#mod{debug_info = Val}, Status};
+ _ ->
+ Text = lists:flatten(io_lib:format("~p", [{Key, Val}])),
+ {Mod, reltool_utils:return_first_error(Status, "Illegal option: " ++ Text)}
+ end,
+ decode(Mod2, KeyVals, Status2);
+decode(#rel{rel_apps = RelApps} = Rel, [RelApp | KeyVals], Status) ->
+ RA =
+ case RelApp of
+ Name when is_atom(Name) ->
+ #rel_app{name = Name, app_type = undefined, incl_apps = []};
+ {Name, Type} when is_atom(Name) ->
+ #rel_app{name = Name, app_type = Type, incl_apps = []};
+ {Name, InclApps} when is_atom(Name), is_list(InclApps) ->
+ #rel_app{name = Name, app_type = undefined, incl_apps = InclApps};
+ {Name, Type, InclApps} when is_atom(Name), is_list(InclApps) ->
+ #rel_app{name = Name, app_type = Type, incl_apps = InclApps};
+ _ ->
+ #rel_app{incl_apps = []}
+ end,
+ IsType = is_type(RA#rel_app.app_type),
+ NonAtoms = [IA || IA <- RA#rel_app.incl_apps, not is_atom(IA)],
+ if
+ IsType, NonAtoms =:= [] ->
+ decode(Rel#rel{rel_apps = RelApps ++ [RA]}, KeyVals, Status);
+ true ->
+ Text = lists:flatten(io_lib:format("~p", [RelApp])),
+ Status2 = reltool_utils:return_first_error(Status, "Illegal option: " ++ Text),
+ decode(Rel, KeyVals, Status2)
+ end;
+decode(Acc, [], Status) ->
+ {Acc, Status};
+decode(Acc, KeyVal, Status) ->
+ Text = lists:flatten(io_lib:format("~p", [KeyVal])),
+ {Acc, reltool_utils:return_first_error(Status, "Illegal option: " ++ Text)}.
+
+is_type(Type) ->
+ case Type of
+ undefined -> true;
+ permanent -> true;
+ transient -> true;
+ temporary -> true;
+ load -> true;
+ none -> true;
+ _ -> false
+ end.
+
+split_escript_name(File) when is_list(File) ->
+ Label = filename:basename(File, ".escript"),
+ {list_to_atom("*escript* " ++ Label), Label}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+refresh(#state{sys = Sys} = S, Force, Status) ->
+ {Sys2, Status2} = merge_config(Sys, Sys#sys{apps = []}, Force, Status),
+ {S#state{sys = Sys2}, Status2}.
+
+merge_config(OldSys, NewSys, Force, Status) ->
+ RootDir = filename:absname(NewSys#sys.root_dir),
+ LibDirs = [filename:absname(D) || D <- NewSys#sys.lib_dirs],
+ Escripts = [filename:absname(E) || E <- NewSys#sys.escripts],
+ {SourceDirs, Status2} =
+ libs_to_dirs(RootDir, LibDirs, Status),
+ MergedApps = merge_app_dirs(SourceDirs, NewSys#sys.apps, OldSys#sys.apps),
+ {AllApps, Status3} =
+ escripts_to_apps(Escripts, MergedApps, OldSys#sys.apps, Status2),
+ {RefreshedApps, Status4} =
+ refresh_apps(OldSys#sys.apps, AllApps, [], Force, Status3),
+ {PatchedApps, Status5} = patch_erts_version(RootDir, RefreshedApps, Status4),
+ Escripts2 = [A#app.active_dir || A <- PatchedApps, A#app.is_escript],
+ NewSys2 = NewSys#sys{root_dir = RootDir,
+ lib_dirs = LibDirs,
+ escripts = Escripts2,
+ apps = PatchedApps},
+ {NewSys2, Status5}.
+
+verify_config(Sys, Status) ->
+ case lists:keymember(Sys#sys.boot_rel, #rel.name, Sys#sys.rels) of
+ true ->
+ lists:foreach(fun(Rel)-> check_rel(Rel, Sys, Status) end, Sys#sys.rels),
+ Status;
+ false ->
+ Text = lists:concat([Sys#sys.boot_rel, ": release is mandatory"]),
+ Status2 = reltool_utils:return_first_error(Status, Text),
+ throw({error, Status2})
+ end.
+
+check_rel(#rel{name = RelName, rel_apps = RelApps}, #sys{apps = Apps}, Status) ->
+ EnsureApp =
+ fun(AppName) ->
+ case lists:keymember(AppName, #rel_app.name, RelApps) of
+ true ->
+ ok;
+ false ->
+ Text = lists:concat([RelName, ": ", AppName, " is not included."]),
+ Status2 = reltool_utils:return_first_error(Status, Text),
+ throw({error, Status2})
+ end
+ end,
+ EnsureApp(kernel),
+ EnsureApp(stdlib),
+ CheckRelApp =
+ fun(#rel_app{name = AppName}) ->
+ case lists:keysearch(AppName, #app.name, Apps) of
+ {value, App} when App#app.is_pre_included ->
+ ok;
+ {value, App} when App#app.is_included ->
+ ok;
+ _ ->
+ Text = lists:concat([RelName, ": uses application ",
+ AppName, " that not is included."]),
+ Status2 = reltool_utils:return_first_error(Status, Text),
+ %% throw BUGBUG: add throw
+ ({error, Status2})
+ end
+ end,
+ lists:foreach(CheckRelApp, RelApps).
+
+patch_erts_version(RootDir, Apps, Status) ->
+ AppName = erts,
+ case lists:keysearch(AppName, #app.name, Apps) of
+ {value, Erts} ->
+ LocalRoot = code:root_dir(),
+ Vsn = Erts#app.vsn,
+ if
+ LocalRoot =:= RootDir, Vsn =:= "" ->
+ Vsn2 = erlang:system_info(version),
+ Erts2 = Erts#app{vsn = Vsn2, label = "erts-" ++ Vsn2},
+ Apps2 = lists:keystore(AppName, #app.name, Apps, Erts2),
+ {Apps2, Status};
+ Vsn =:= "" ->
+ {Apps, reltool_utils:add_warning(Status, "erts has no version")};
+ true ->
+ {Apps, Status}
+ end;
+ false ->
+ Text = "erts cannnot be found in the root directory " ++ RootDir,
+ Status2 = reltool_utils:return_first_error(Status, Text),
+ {Apps, Status2}
+ end.
+
+libs_to_dirs(RootDir, LibDirs, Status) ->
+ case file:list_dir(RootDir) of
+ {ok, RootFiles} ->
+ RootLibDir = filename:join([RootDir, "lib"]),
+ AllLibDirs = [RootLibDir | LibDirs],
+ case AllLibDirs -- lists:usort(AllLibDirs) of
+ [] ->
+ Fun = fun(Base) ->
+ AppDir = filename:join([RootLibDir, Base]),
+ case filelib:is_dir(filename:join([AppDir, "ebin"]), erl_prim_loader) of
+ true ->
+ AppDir;
+ false ->
+ filename:join([RootDir, Base, "preloaded"])
+ end
+ end,
+ ErtsFiles = [{erts, Fun(F)} || F <- RootFiles, lists:prefix("erts", F)],
+ app_dirs2(AllLibDirs, [ErtsFiles], Status);
+ [Duplicate | _] ->
+ {[], reltool_utils:return_first_error(Status, "Duplicate library: " ++ Duplicate)}
+ end;
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ {[], reltool_utils:return_first_error(Status, "Missing root library " ++ RootDir ++ ": " ++ Text)}
+ end.
+
+app_dirs2([Lib | Libs], Acc, Status) ->
+ case file:list_dir(Lib) of
+ {ok, Files} ->
+ Filter =
+ fun(Base) ->
+ AppDir = filename:join([Lib, Base]),
+ EbinDir = filename:join([AppDir, "ebin"]),
+ case filelib:is_dir(EbinDir, erl_prim_loader) of
+ true ->
+ {Name, _Vsn} = reltool_utils:split_app_name(Base),
+ case Name of
+ erts -> false;
+ _ -> {true, {Name, AppDir}}
+ end;
+ false ->
+ false
+ end
+ end,
+ Files2 = lists:zf(Filter, Files),
+ app_dirs2(Libs, [Files2 | Acc], Status);
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ {[], reltool_utils:return_first_error(Status, "Illegal library " ++ Lib ++ ": " ++ Text)}
+ end;
+app_dirs2([], Acc, Status) ->
+ {lists:sort(lists:append(Acc)), Status}.
+
+escripts_to_apps([Escript | Escripts], Apps, OldApps, Status) ->
+ {EscriptAppName, _Label} = split_escript_name(Escript),
+ Ext = code:objfile_extension(),
+ Fun = fun(FullName, _GetInfo, GetBin, {FileAcc, StatusAcc}) ->
+ Components = filename:split(FullName),
+ case Components of
+ [AppLabel, "ebin", File] ->
+ case filename:extension(File) of
+ ".app" ->
+ {AppName, DefaultVsn} = reltool_utils:split_app_name(AppLabel),
+ AppFileName = filename:join([Escript, FullName]),
+ {Info, StatusAcc2} =
+ read_app_info(GetBin(), AppFileName, AppName, DefaultVsn, Status),
+ Dir = filename:join([Escript, AppName]),
+ {[{AppName, app, Dir, Info} | FileAcc], StatusAcc2};
+ E when E =:= Ext ->
+ {AppName, _} = reltool_utils:split_app_name(AppLabel),
+ Mod = init_mod(AppName, File, {File, GetBin()}, Ext),
+ Dir = filename:join([Escript, AppName]),
+ {[{AppName, mod, Dir, Mod} | FileAcc], StatusAcc};
+ _ ->
+ {FileAcc, StatusAcc}
+ end;
+ ["."] ->
+ Bin = GetBin(),
+ {ok, {ModName, _}} = beam_lib:version(Bin),
+ ModStr = atom_to_list(ModName) ++ Ext,
+ Mod = init_mod(EscriptAppName, ModStr, {ModStr, GetBin()}, Ext),
+ {[{EscriptAppName, mod, Escript, Mod} | FileAcc], StatusAcc};
+ [File] ->
+ case filename:extension(File) of
+ E when E =:= Ext ->
+ Mod = init_mod(EscriptAppName, File, {File, GetBin()}, Ext),
+ {[{EscriptAppName, mod, File, Mod} | FileAcc], StatusAcc};
+ _ ->
+ {FileAcc, StatusAcc}
+ end;
+ _ ->
+ {FileAcc, StatusAcc}
+ end
+ end,
+ try
+ case escript:foldl(Fun, {[], Status}, Escript) of
+ {ok, {Files, Status2}} ->
+ {Apps2, Status3} = files_to_apps(Escript, lists:sort(Files), Apps, Apps, OldApps, Status2),
+ escripts_to_apps(Escripts, Apps2, OldApps, Status3);
+ {error, Reason} ->
+ Text = lists:flatten(io_lib:format("~p", [Reason])),
+ {[], reltool_utils:return_first_error(Status, "Illegal escript " ++ Escript ++ ": " ++ Text)}
+ end
+ catch
+ throw:Reason2 when is_list(Reason2) ->
+ {[], reltool_utils:return_first_error(Status, "Illegal escript " ++ Escript ++ ": " ++ Reason2)}
+ end;
+escripts_to_apps([], Apps, _OldApps, Status) ->
+ {Apps, Status}.
+
+%% Assume that all files for an app are in consecutive order
+%% Assume the app info is before the mods
+files_to_apps(Escript, [{AppName, Type, Dir, ModOrInfo} | Files] = AllFiles, Acc, Apps, OldApps, Status) ->
+ case Type of
+ mod ->
+ case Acc of
+ [] ->
+ Info = missing_app_info(""),
+ {NewApp, Status2} = merge_escript_app(AppName, Dir, Info, [ModOrInfo], Apps, OldApps, Status),
+ files_to_apps(Escript, AllFiles, [NewApp | Acc], Apps, OldApps, Status2);
+ [App | Acc2] when App#app.name =:= ModOrInfo#mod.app_name ->
+ App2 = App#app{mods = [ModOrInfo | App#app.mods]},
+ files_to_apps(Escript, Files, [App2 | Acc2], Apps, OldApps, Status);
+ [App | Acc2] ->
+ PrevApp = App#app{mods = lists:keysort(#mod.name, App#app.mods)},
+ Info = missing_app_info(""),
+ {NewApp, Status2} = merge_escript_app(AppName, Dir, Info, [ModOrInfo], Apps, OldApps, Status),
+ files_to_apps(Escript, Files, [NewApp, PrevApp | Acc2], Apps, OldApps, Status2)
+ end;
+ app ->
+ {App, Status2} = merge_escript_app(AppName, Dir, ModOrInfo, [], Apps, OldApps, Status),
+ files_to_apps(Escript, Files, [App | Acc], Apps, OldApps, Status2)
+ end;
+files_to_apps(_Escript, [], Acc, _Apps, _OldApps, Status) ->
+ {lists:keysort(#app.name, Acc), Status}.
+
+merge_escript_app(AppName, Dir, Info, Mods, Apps, OldApps, Status) ->
+ case lists:keysearch(AppName, #app.name, OldApps) of
+ {value, App} ->
+ ok;
+ false ->
+ App = default_app(AppName, Dir)
+ end,
+ App2 = App#app{is_escript = true,
+ label = filename:basename(Dir, ".escript"),
+ info = Info,
+ mods = Mods,
+ active_dir = Dir,
+ sorted_dirs = [Dir]},
+ case lists:keysearch(AppName, #app.name, Apps) of
+ {value, _} ->
+ Error = lists:concat([AppName, ": Application name clash. ",
+ "Escript ", Dir," contains application ", AppName, "."]),
+ {App2, reltool_utils:return_first_error(Status, Error)};
+ false ->
+ {App2, Status}
+ end.
+
+merge_app_dirs([{Name, Dir} | Rest], [App | Apps], OldApps)
+ when App#app.name =:= Name ->
+ %% Add new dir to app
+ App2 = App#app{sorted_dirs = [Dir | App#app.sorted_dirs]},
+ merge_app_dirs(Rest, [App2 | Apps], OldApps);
+merge_app_dirs([{Name, Dir} | Rest], Apps, OldApps) ->
+ %% Initate app
+ Apps2 = sort_app_dirs(Apps),
+ Apps4 =
+ case lists:keysearch(Name, #app.name, Apps) of
+ false ->
+ case lists:keysearch(Name, #app.name, OldApps) of
+ {value, OldApp} when OldApp#app.active_dir =:= Dir ->
+ [OldApp | Apps2];
+ {value, OldApp} ->
+ App =
+ case filter_app(OldApp) of
+ {true, NewApp} ->
+ NewApp#app{active_dir = Dir, sorted_dirs = [Dir]};
+ false ->
+ default_app(Name, Dir)
+ end,
+ [App | Apps2];
+ false ->
+ App = default_app(Name, Dir),
+ [App | Apps2]
+ end;
+ {value, OldApp} ->
+ Apps3 = lists:keydelete(Name, #app.name, Apps2),
+ App = OldApp#app{sorted_dirs = [Dir | OldApp#app.sorted_dirs]},
+ [App | Apps3]
+ end,
+ merge_app_dirs(Rest, Apps4, OldApps);
+merge_app_dirs([], Apps, _OldApps) ->
+ Apps2 = sort_app_dirs(Apps),
+ lists:reverse(Apps2).
+
+sort_app_dirs([#app{sorted_dirs = Dirs} = App | Acc]) ->
+ SortedDirs = lists:sort(fun reltool_utils:app_dir_test/2, Dirs),
+ case SortedDirs of
+ [ActiveDir | _] -> ok;
+ [] -> ActiveDir = undefined
+ end,
+ [App#app{active_dir = ActiveDir, sorted_dirs = SortedDirs} | Acc];
+sort_app_dirs([]) ->
+ [].
+
+default_app(Name, Dir) ->
+ App = default_app(Name),
+ App#app{active_dir = Dir,
+ sorted_dirs = [Dir]}.
+
+default_app(Name) ->
+ #app{name = Name,
+ is_escript = false,
+ use_selected_vsn = undefined,
+ active_dir = undefined,
+ sorted_dirs = [],
+ vsn = undefined,
+ label = undefined,
+ info = undefined,
+ mods = [],
+
+ mod_cond = undefined,
+ incl_cond = undefined,
+
+ status = missing,
+ uses_mods = undefined,
+ is_pre_included = undefined,
+ is_included = undefined}.
+
+%% Assume that the application are sorted
+refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status) when New#app.name =:= Old#app.name ->
+ {Info, ActiveDir, Status2} = ensure_app_info(New, Status),
+ OptLabel =
+ case Info#app_info.vsn =:= New#app.vsn of
+ true -> New#app.label;
+ false -> undefined % Cause refresh
+ end,
+ {Refreshed, Status3} =
+ refresh_app(New#app{label = OptLabel,
+ active_dir = ActiveDir,
+ vsn = Info#app_info.vsn,
+ info = Info},
+ Force,
+ Status2),
+ refresh_apps(OldApps, NewApps, [Refreshed | Acc], Force, Status3);
+refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status) when New#app.name < Old#app.name ->
+ %% No old app version exists. Use new as is.
+ %% BUGBUG: Issue warning if the active_dir is not defined
+ {New2, Status2} = refresh_app(New, Force, Status),
+ refresh_apps([Old | OldApps], NewApps, [New2 | Acc], Force, Status2);
+refresh_apps([Old | OldApps], [New | NewApps], Acc, Force, Status) when New#app.name > Old#app.name ->
+ %% No new version. Remove the old.
+ Status2 =
+ case Old#app.name =:= ?MISSING_APP of
+ true ->
+ Status;
+ false ->
+ Warning = lists:concat([Old#app.name, ": The source dirs does not contain the application anymore."]),
+ reltool_utils:add_warning(Status, Warning)
+ end,
+ refresh_apps(OldApps, [New | NewApps], Acc, Force, Status2);
+refresh_apps([], [New | NewApps], Acc, Force, Status) ->
+ %% No old app version exists. Use new as is.
+ {New2, Status2} = refresh_app(New, Force, Status),
+ refresh_apps([], NewApps, [New2 | Acc], Force, Status2);
+refresh_apps([Old | OldApps], [], Acc, Force, Status) ->
+ %% No new version. Remove the old.
+ Status2 =
+ case Old#app.name =:= ?MISSING_APP of
+ true ->
+ Status;
+ false ->
+ Warning = lists:concat([Old#app.name, ": The source dirs ",
+ "does not contain the application anymore."]),
+ reltool_utils:add_warning(Status, Warning)
+ end,
+ refresh_apps(OldApps, [], Acc, Force, Status2);
+refresh_apps([], [], Acc, _Force, Status) ->
+ {lists:reverse(Acc), Status}.
+
+ensure_app_info(#app{is_escript = true, active_dir = Dir, info = Info}, Status) ->
+ {Info, Dir, Status};
+ensure_app_info(#app{name = Name, sorted_dirs = []}, Status) ->
+ Error = lists:concat([Name, ": Missing application directory."]),
+ Status2 = reltool_utils:return_first_error(Status, Error),
+ {missing_app_info(""), undefined, Status2};
+ensure_app_info(#app{name = Name, vsn = Vsn, sorted_dirs = Dirs, info = undefined}, Status) ->
+ ReadInfo =
+ fun(Dir, StatusAcc) ->
+ Base = get_base(Name, Dir),
+ Ebin = filename:join([Dir, "ebin"]),
+ {_, DefaultVsn} = reltool_utils:split_app_name(Base),
+ AppFile = filename:join([Ebin, atom_to_list(Name) ++ ".app"]),
+ read_app_info(AppFile, AppFile, Name, DefaultVsn, StatusAcc)
+ end,
+ {AllInfo, Status2} = lists:mapfoldl(ReadInfo, Status, Dirs),
+ AllVsns = [I#app_info.vsn || I <- AllInfo],
+ Status3 =
+ case AllVsns -- lists:usort(AllVsns) of
+ [] ->
+ %% No redundant info
+ Status2;
+ [BadVsn | _] ->
+ Error2 = lists:concat([Name, ": Application version clash. ",
+ "Multiple directories contains version \"", BadVsn, "\"."]),
+ reltool_utils:return_first_error(Status2, Error2)
+ end,
+ FirstInfo = hd(AllInfo),
+ FirstDir = hd(Dirs),
+ if
+ Vsn =:= undefined ->
+ {FirstInfo, FirstDir, Status3};
+ Vsn =:= FirstInfo#app_info.vsn ->
+ {FirstInfo, FirstDir, Status3};
+ true ->
+ case find_vsn(Vsn, AllInfo, Dirs) of
+ {Info, VsnDir} ->
+ {Info, VsnDir, Status3};
+ false ->
+ Error3 = lists:concat([Name, ": No application directory contains selected version \"", Vsn, "\"."]),
+ Status4 = reltool_utils:return_first_error(Status3, Error3),
+ {FirstInfo, FirstDir, Status4}
+ end
+ end;
+ensure_app_info(#app{active_dir = Dir, info = Info}, Status) ->
+ {Info, Dir, Status}.
+
+find_vsn(Vsn, [#app_info{vsn = Vsn} = Info | _], [Dir | _]) ->
+ {Info, Dir};
+find_vsn(Vsn, [_ | MoreInfo], [_ | MoreDirs]) ->
+ find_vsn(Vsn, MoreInfo, MoreDirs);
+find_vsn(_, [], []) ->
+ false.
+
+get_base(Name, Dir) ->
+ case Name of
+ erts ->
+ case filename:basename(Dir) of
+ "preloaded" ->
+ filename:basename(filename:dirname(Dir));
+ TmpBase ->
+ TmpBase
+ end;
+ _ ->
+ filename:basename(Dir)
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% sys callbacks
+
+system_continue(_Parent, _Debug, S) ->
+ ?MODULE:loop(S).
+
+system_terminate(Reason, _Parent, _Debug, _S) ->
+ exit(Reason).
+
+system_code_change(S,_Module,_OldVsn,_Extra) ->
+ {ok, S}.
diff --git a/lib/reltool/src/reltool_sys_win.erl b/lib/reltool/src/reltool_sys_win.erl
new file mode 100644
index 0000000000..ea80ab7e85
--- /dev/null
+++ b/lib/reltool/src/reltool_sys_win.erl
@@ -0,0 +1,1292 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(reltool_sys_win).
+
+%% Public
+-export([start_link/1, get_server/1, set_app/2, open_app/2]).
+
+%% Internal
+-export([init/1, loop/1]).
+
+%% sys callback functions
+-export([
+ system_continue/3,
+ system_terminate/4,
+ system_code_change/4
+ ]).
+
+-include_lib("wx/include/wx.hrl").
+-include("reltool.hrl").
+
+-record(state,
+ {parent_pid,
+ server_pid,
+ app_wins,
+ sys,
+ common,
+ config_file,
+ target_dir,
+ boot_dir,
+ frame,
+ panel,
+ book,
+ rel_book,
+ lib_tree,
+ status_bar,
+ popup_menu,
+ source,
+ whitelist,
+ blacklist,
+ derived,
+ fgraph_wins
+ }).
+
+-define(WIN_WIDTH, 800).
+-define(WIN_HEIGHT, 600).
+
+-define(CLOSE_ITEM, ?wxID_EXIT). %% Use OS specific version if available
+-define(ABOUT_ITEM, ?wxID_ABOUT). %% Use OS specific
+-define(CONTENTS_ITEM, 300).
+-define(APP_GRAPH_ITEM, 301).
+-define(MOD_GRAPH_ITEM, 302).
+-define(LOAD_CONFIG_ITEM, 303).
+-define(SAVE_CONFIG_NODEF_NODER_ITEM, 304).
+-define(SAVE_CONFIG_NODEF_DER_ITEM, 305).
+-define(SAVE_CONFIG_DEF_NODER_ITEM, 306).
+-define(SAVE_CONFIG_DEF_DER_ITEM, 307).
+-define(UNDO_CONFIG_ITEM, 308).
+-define(RESET_CONFIG_ITEM, 309).
+-define(GEN_REL_FILES_ITEM, 310).
+-define(GEN_TARGET_ITEM, 311).
+
+-define(APP_PAGE, "Applications").
+-define(LIB_PAGE, "Libraries").
+-define(SYS_PAGE, "System settings").
+-define(REL_PAGE, "Releases").
+
+-define(APPS_APP_COL, 0).
+-define(source, "Available").
+-define(whitelist, "Included").
+-define(blacklist, "Excluded").
+-define(derived, "Derived").
+
+-record(root_data, {dir}).
+-record(lib_data, {dir, tree, item}).
+-record(escript_data, {file, tree, item}).
+-record(app_data, {name, dir}).
+-record(app_win, {name, pid}).
+-record(fgraph_win, {frame, pid}).
+-record(root_popup, {dir, choices, tree, item}).
+-record(lib_popup, {dir, choices, tree, item}).
+-record(escript_popup, {file, choices, tree, item}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Client
+
+start_link(Opts) ->
+ proc_lib:start_link(?MODULE, init, [[{parent, self()} | Opts]], infinity, []).
+
+get_server(Pid) ->
+ reltool_utils:call(Pid, get_server).
+
+set_app(Pid, App) ->
+ reltool_utils:call(Pid, {set_app, App}).
+
+open_app(Pid, AppName) ->
+ reltool_utils:call(Pid, {open_app, AppName}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Server
+
+init(Options) ->
+ try
+ do_init(Options)
+ catch
+ error:Reason ->
+ exit({Reason, erlang:get_stacktrace()})
+ end.
+
+do_init([{parent, Parent} | Options]) ->
+ case reltool_server:start_link(Options) of
+ {ok, ServerPid, C, Sys} ->
+ process_flag(trap_exit, C#common.trap_exit),
+ S = #state{parent_pid = Parent,
+ server_pid = ServerPid,
+ common = C,
+ config_file = filename:absname("config.reltool"),
+ target_dir = filename:absname("reltool_target_dir"),
+ app_wins = [],
+ sys = Sys,
+ fgraph_wins = []},
+ wx:new(),
+ wx:debug(C#common.wx_debug),
+ S2 = create_window(S),
+
+ %% wx_misc:beginBusyCursor(),
+ case reltool_server:get_status(ServerPid) of
+ {ok, Warnings} ->
+ exit_dialog(Warnings),
+ {ok, Sys2} = reltool_server:get_sys(ServerPid),
+ S3 = S2#state{sys = Sys2},
+ S5 = wx:batch(fun() ->
+ Title = atom_to_list(?APPLICATION),
+ wxFrame:setTitle(S3#state.frame, Title),
+ %% wxFrame:setMinSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}),
+ wxStatusBar:setStatusText(S3#state.status_bar, "Done."),
+ S4 = redraw_apps(S3),
+ redraw_libs(S4)
+ end),
+ %% wx_misc:endBusyCursor(),
+ %% wxFrame:destroy(Frame),
+ proc_lib:init_ack(S#state.parent_pid, {ok, self()}),
+ loop(S5);
+ {error, Reason} ->
+ io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]),
+ exit(Reason)
+ end;
+ {error, Reason} ->
+ io:format("~p(~p): <ERROR> ~p\n", [?MODULE, ?LINE, Reason]),
+ exit(Reason)
+ end.
+
+exit_dialog([]) ->
+ ok;
+exit_dialog(Warnings) ->
+ Question = "Do you want to continue despite these warnings?",
+ Details = lists:flatten([[W, $\n] || W <- Warnings]),
+ case question_dialog(Question, Details) of
+ ?wxID_OK ->
+ ok;
+ ?wxID_CANCEL ->
+ io:format("~p(~p): <ERROR> ~s\n", [?MODULE, ?LINE, Details]),
+ exit(Details)
+ end.
+
+loop(S) ->
+ receive
+ {system, From, Msg} ->
+ Common = S#state.common,
+ sys:handle_system_msg(Msg, From, S#state.parent_pid, ?MODULE, Common#common.sys_debug, S);
+ #wx{obj = ObjRef,
+ event = #wxClose{type = close_window}} = Msg ->
+ if
+ ObjRef =:= S#state.frame ->
+ wxFrame:destroy(ObjRef),
+ exit(shutdown);
+ true ->
+ FWs = S#state.fgraph_wins,
+ case lists:keysearch(ObjRef, #fgraph_win.frame, FWs) of
+ {value, FW} ->
+ reltool_fgraph_win:stop(FW#fgraph_win.pid, shutdown),
+ wxFrame:destroy(ObjRef),
+ FWs2 = lists:keydelete(ObjRef, #fgraph_win.frame, FWs),
+ ?MODULE:loop(S#state{fgraph_wins = FWs2});
+ false ->
+ error_logger:format("~p~p got unexpected message:\n\t~p\n",
+ [?MODULE, self(), Msg]),
+ ?MODULE:loop(S)
+ end
+ end;
+ #wx{id = ?CLOSE_ITEM, event = #wxCommand{type = command_menu_selected}, userData = main_window} ->
+ wxFrame:destroy(S#state.frame),
+ exit(shutdown);
+ #wx{event = #wxSize{}} = Wx ->
+ Wx2 = reltool_utils:get_latest_resize(Wx),
+ S2 = handle_event(S, Wx2),
+ ?MODULE:loop(S2);
+ #wx{} = Wx ->
+ S2 = handle_event(S, Wx),
+ ?MODULE:loop(S2);
+ {call, ReplyTo, Ref, get_server} ->
+ reltool_utils:reply(ReplyTo, Ref, {ok, S#state.server_pid}),
+ ?MODULE:loop(S);
+ {call, ReplyTo, Ref, {set_app, NewApp}} ->
+ {ok, AnalysedApp, S2} = do_set_app(S, NewApp),
+ reltool_utils:reply(ReplyTo, Ref, {ok, AnalysedApp}),
+ ?MODULE:loop(S2);
+ {call, ReplyTo, Ref, {open_app, AppName}} ->
+ S2 = do_open_app(S, AppName),
+ {value, #app_win{pid = AppPid}} = lists:keysearch(AppName, #app_win.name, S2#state.app_wins),
+ reltool_utils:reply(ReplyTo, Ref, {ok, AppPid}),
+ ?MODULE:loop(S2);
+ {'EXIT', Pid, Reason} when Pid =:= S#state.parent_pid ->
+ [reltool_fgraph_win:stop(FW#fgraph_win.pid, Reason) || FW <- S#state.fgraph_wins],
+ exit(Reason);
+ {'EXIT', _Pid, _Reason} = Exit ->
+ {FWs, AWs} = handle_child_exit(Exit, S#state.fgraph_wins, S#state.app_wins),
+ ?MODULE:loop(S#state{fgraph_wins = FWs, app_wins = AWs});
+ Msg ->
+ error_logger:format("~p~p got unexpected message:\n\t~p\n",
+ [?MODULE, self(), Msg]),
+ ?MODULE:loop(S)
+ end.
+
+handle_child_exit({'EXIT', Pid, _Reason} = Exit, FWs, AWs) ->
+ case lists:keymember(Pid, #fgraph_win.pid, FWs) of
+ true ->
+ msg_warning(Exit, forcegraph_window),
+ {lists:keydelete(Pid, #fgraph_win.pid, FWs), AWs};
+ false ->
+ case lists:keymember(Pid, #app_win.pid, AWs) of
+ true ->
+ msg_warning(Exit, application_window),
+ {FWs, lists:keydelete(Pid, #app_win.pid, AWs)};
+ false ->
+ msg_warning(Exit, unknown),
+ {FWs, AWs}
+ end
+ end.
+
+msg_warning({'EXIT', _Pid, shutdown}, Type) when Type =/= unknown ->
+ ok;
+msg_warning(Exit, Type) ->
+ error_logger:format("~p~p got unexpected message (~p):\n\t~p\n",
+ [?MODULE, self(), Type, Exit]).
+
+create_window(S) ->
+ Title = lists:concat([?APPLICATION, " - starting up"]),
+ Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, [{size, {?WIN_WIDTH, ?WIN_HEIGHT}}]),
+ %%wxFrame:setSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}),
+ %% wxFrame:setMinSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}),
+ Bar = wxFrame:createStatusBar(Frame,[]),
+ wxStatusBar:setStatusText(Bar, "Processing libraries..."),
+ %% Label = wxStaticText:new(Panel, ?wxID_ANY, Text, [{style, ?wxTE_READONLY}]),
+ %% Sizer = wxBoxSizer:new(?wxVERTICAL),
+ %% wxSizer:add(Sizer, Label, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ %% wxPanel:setSizer(Panel, Sizer),
+ %% wxSizer:fit(Sizer, Frame),
+ %% wxSizer:setSizeHints(Sizer, Frame),
+
+ %% Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, []),
+ %% Frame = S#state.frame,
+ wxToolTip:setDelay(3000),
+ Panel = wxPanel:new(Frame, []),
+ %% Bar = wxFrame:createStatusBar(Frame,[]),
+ create_menubar(Frame),
+
+ Book = wxNotebook:new(Panel, ?wxID_ANY, []),
+ S2 = S#state{frame = Frame, panel = Panel, book = Book, status_bar = Bar},
+ S3 = lists:foldl(fun(Fun, Acc) -> Fun(Acc) end,
+ S2,
+ [
+ fun create_app_page/1,
+ fun create_lib_page/1,
+ fun create_main_release_page/1,
+ fun create_config_page/1
+ ]),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Sizer, Book, [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ wxPanel:setSizer(Panel, Sizer),
+ wxSizer:fit(Sizer, Frame),
+ wxSizer:setSizeHints(Sizer, Frame),
+ wxFrame:connect(Frame, close_window),
+
+ wxFrame:show(Frame),
+ S3.
+
+create_menubar(Frame) ->
+ MenuBar = wxMenuBar:new(),
+ File = wxMenu:new([]),
+ Help = wxMenu:new([]),
+ wxMenuBar:append(MenuBar, File, "File" ),
+ wxMenu:append(File, ?APP_GRAPH_ITEM, "Display application dependency graph" ),
+ wxMenu:append(File, ?MOD_GRAPH_ITEM, "Display module dependency graph" ),
+ wxMenu:appendSeparator(File),
+ wxMenu:append(File, ?RESET_CONFIG_ITEM, "Reset configuration to default" ),
+ wxMenu:append(File, ?UNDO_CONFIG_ITEM, "Undo configuration (toggle)" ),
+ wxMenu:append(File, ?LOAD_CONFIG_ITEM, "Load configuration" ),
+ Save = wxMenu:new(),
+ wxMenu:append(Save, ?SAVE_CONFIG_NODEF_NODER_ITEM, "Save explicit configuration (neither defaults nor derivates)"),
+ wxMenu:append(Save, ?SAVE_CONFIG_DEF_NODER_ITEM , "Save configuration defaults (defaults only)"),
+ wxMenu:append(Save, ?SAVE_CONFIG_NODEF_DER_ITEM, "Save configuration derivates (derivates only))"),
+ wxMenu:append(Save, ?SAVE_CONFIG_DEF_DER_ITEM, "Save extended configuration (both defaults and derivates)"),
+
+ wxMenu:append(File, ?wxID_ANY, "Save configuration", Save),
+ wxMenu:appendSeparator(File),
+ wxMenu:append(File, ?GEN_REL_FILES_ITEM, "Generate rel, script and boot files" ),
+ wxMenu:append(File, ?GEN_TARGET_ITEM, "Generate target system" ),
+ wxMenu:appendSeparator(File),
+ wxMenu:append(File, ?CLOSE_ITEM, "Close" ),
+ wxMenuBar:append(MenuBar, Help, "Help" ),
+ wxMenu:append(Help, ?CONTENTS_ITEM, "Contents" ),
+ wxMenu:append(Help, ?ABOUT_ITEM, "About" ),
+ wxFrame:setMenuBar(Frame, MenuBar),
+ wxEvtHandler:connect(Frame,
+ command_menu_selected,
+ [{userData, main_window}]),
+ wxEvtHandler:connect(File, menu_close),
+ wxEvtHandler:connect(Help, menu_close),
+ MenuBar.
+
+create_app_page(#state{book = Book} = S) ->
+ Panel = wxPanel:new(Book, []),
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+
+ SourceCtrl = create_app_list_ctrl(Panel, Sizer, ?source,
+ whitelist_add, blacklist_add),
+ wxSizer:add(Sizer,
+ wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ WhiteCtrl = create_app_list_ctrl(Panel, Sizer, ?whitelist,
+ whitelist_del, blacklist_add),
+ wxSizer:add(Sizer,
+ wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ BlackCtrl = create_app_list_ctrl(Panel, Sizer, ?blacklist,
+ whitelist_add, blacklist_del),
+ wxSizer:add(Sizer,
+ wxStaticLine:new(Panel, [{style, ?wxLI_VERTICAL}]),
+ [{border, 2}, {flag, ?wxALL bor ?wxEXPAND}]),
+ DerivedCtrl = create_app_list_ctrl(Panel, Sizer, ?derived,
+ whitelist_add, blacklist_add),
+ %% S3 = redraw_apps(S2),
+ wxPanel:setSizer(Panel, Sizer),
+ wxNotebook:addPage(Book, Panel, ?APP_PAGE, []),
+ S#state{source = SourceCtrl,
+ whitelist = WhiteCtrl,
+ blacklist = BlackCtrl,
+ derived = DerivedCtrl}.
+
+create_app_list_ctrl(Panel, OuterSz, Title, Tick, Cross) ->
+ %% Create list control
+ Width = lists:max([100, ?WIN_WIDTH - 40]) div 4,
+ Height = lists:max([100, ?WIN_HEIGHT - 100]),
+ ListCtrl = wxListCtrl:new(Panel,
+ [{style,
+ ?wxLC_REPORT bor
+ %% ?wxLC_SORT_ASCENDING bor
+ %% ?wxLC_SINGLE_SEL bor
+ ?wxVSCROLL},
+ {size, {Width, Height}}]),
+ ToolTip = "Select application(s) or open separate application window with a double click.",
+ wxListCtrl:setToolTip(ListCtrl, ToolTip),
+
+ %% Prep images
+ reltool_utils:assign_image_list(ListCtrl),
+
+ %% Prep column label
+ ListItem = wxListItem:new(),
+ wxListItem:setAlign(ListItem, ?wxLIST_FORMAT_LEFT),
+ wxListItem:setText(ListItem, Title),
+ wxListCtrl:insertColumn(ListCtrl, ?APPS_APP_COL, ListItem),
+ wxListItem:destroy(ListItem),
+
+ %% Create button
+ ButtonSz = wxBoxSizer:new(?wxHORIZONTAL),
+ create_button(Panel, ButtonSz, ListCtrl, Title, "wxART_TICK_MARK", Tick),
+ create_button(Panel, ButtonSz, ListCtrl, Title, "wxART_CROSS_MARK", Cross),
+
+
+ InnerSz = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(InnerSz,
+ ListCtrl,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+ wxSizer:add(InnerSz,
+ ButtonSz,
+ [{flag, ?wxEXPAND}]),
+ wxSizer:add(OuterSz,
+ InnerSz,
+ [{flag, ?wxEXPAND}, {proportion, 1}]),
+
+ %% Subscribe on events
+ wxEvtHandler:connect(ListCtrl, size, [{skip, true}, {userData, app_list_ctrl}]),
+ wxEvtHandler:connect(ListCtrl, command_list_item_activated),
+ wxWindow:connect(ListCtrl, enter_window),
+
+ ListCtrl.
+
+%% create_button(_Panel, Sizer, _ListCtrl, _BitMapName, _Tag, undefined) ->
+%% wxSizer:addStretchSpacer(Sizer);
+create_button(Panel, Sizer, ListCtrl, Title, BitMapName, Action) ->
+ BitMap = wxArtProvider:getBitmap(BitMapName),
+ Button = wxBitmapButton:new(Panel, ?wxID_ANY, BitMap, []),
+ ToolTip = action_to_tool_tip(Title, Action),
+ wxBitmapButton:setToolTip(Button, ToolTip),
+ Options = [{userData, {app_button, Action, ListCtrl}}],
+ wxEvtHandler:connect(Button, command_button_clicked, Options),
+ wxSizer:add(Sizer,
+ Button,
+ [{border, 2},
+ {flag, ?wxALL},
+ {proportion, 1}]).
+
+action_to_tool_tip(Label, Action) ->
+ case Action of
+ whitelist_add when Label =:= ?whitelist ->
+ "Remove selected application(s) from whitelist.";
+ whitelist_add ->
+ "Add selected application(s) to whitelist.";
+ whitelist_del ->
+ "Remove selected application(s)from whitelist.";
+ blacklist_add when Label =:= ?blacklist ->
+ "Remove selected application(s) from blacklist.";
+ blacklist_add ->
+ "Add selected application(s) to blacklist.";
+ blacklist_del ->
+ "Remove selected application(s) from blacklist."
+ end.
+
+create_lib_page(#state{book = Book} = S) ->
+ Panel = wxPanel:new(Book, []),
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+
+ Tree = wxTreeCtrl:new(Panel, [{style , ?wxTR_HAS_BUTTONS bor ?wxTR_HIDE_ROOT}]),
+ ToolTip = "Edit application sources.",
+ wxBitmapButton:setToolTip(Tree, ToolTip),
+
+ wxFrame:connect(Tree, command_tree_item_activated),
+ wxFrame:connect(Tree, command_tree_item_right_click),
+
+ wxSizer:add(Sizer,
+ Tree,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+ wxPanel:setSizer(Panel, Sizer),
+ wxNotebook:addPage(Book, Panel, ?LIB_PAGE, []),
+ S#state{lib_tree = Tree}.
+
+redraw_libs(#state{lib_tree = Tree, sys = Sys} = S) ->
+ wxTreeCtrl:deleteAllItems(Tree),
+
+ Top = wxTreeCtrl:addRoot(Tree, "Sources", []),
+ {ok, Erts} = reltool_server:get_app(S#state.server_pid, erts),
+ append_root(Tree, Top, Sys#sys.root_dir, Erts),
+
+ LibItem = wxTreeCtrl:appendItem(Tree, Top, "Library directories", []),
+ LibData = #lib_data{dir = undefined, tree = Tree, item = LibItem},
+ wxTreeCtrl:setItemData(Tree, LibItem, LibData),
+ [append_lib(Tree, LibItem, Dir) || Dir <- Sys#sys.lib_dirs],
+
+ EscriptItem = append_item(Tree, Top, "Escript files", undefined),
+ EscriptData = #escript_data{file = undefined, tree = Tree, item = EscriptItem},
+ wxTreeCtrl:setItemData(Tree,EscriptItem, EscriptData),
+ [append_escript(Tree, EscriptItem, File) || File <- Sys#sys.escripts],
+ wxTreeCtrl:expand(Tree, LibItem),
+ wxTreeCtrl:expand(Tree, EscriptItem),
+ S.
+
+append_root(Tree, Parent, Dir, Erts) ->
+ Top = append_item(Tree, Parent, "Root directory", undefined),
+ Data = #root_data{dir = Dir},
+ RootItem = append_item(Tree, Top, Dir, Data),
+ ErtsItem = append_item(Tree, RootItem, "erts", undefined),
+ [append_app(Tree, ErtsItem, filename:basename(filename:dirname(D)), D)
+ || D <- Erts#app.sorted_dirs],
+ LibItem = append_item(Tree, RootItem, "lib", undefined),
+ LibDir = filename:join([Dir, "lib"]),
+ LibDirs = reltool_utils:lib_dirs(LibDir),
+ AppDirs = lists:sort(fun reltool_utils:app_dir_test/2, LibDirs),
+ [append_app(Tree, LibItem, D, LibDir) || D <- AppDirs],
+ wxTreeCtrl:expand(Tree, Top),
+ RootItem.
+
+append_lib(Tree, Parent, Dir) ->
+ Item = wxTreeCtrl:appendItem(Tree, Parent, Dir, []),
+ Data = #lib_data{dir = Dir, tree = Tree, item = Item},
+ wxTreeCtrl:setItemData(Tree, Item, Data),
+ append_apps(Tree, Item, Dir).
+
+append_apps(Tree, Item, Dir) ->
+ AppDirs = lists:sort(fun reltool_utils:app_dir_test/2,
+ reltool_utils:lib_dirs(Dir)),
+ [append_app(Tree, Item, D, Dir) || D <- AppDirs],
+ Item.
+
+append_app(Tree, Parent, Base, Dir) ->
+ Data = #app_data{name = Base, dir = Dir},
+ append_item(Tree, Parent, Base, Data).
+
+append_escript(Tree, Parent, File) ->
+ Data = #escript_data{file = File},
+ append_item(Tree, Parent, File, Data).
+
+append_item(Tree, Parent, Label, Data) ->
+ Item = wxTreeCtrl:appendItem(Tree, Parent, Label, []),
+ wxTreeCtrl:setItemData(Tree, Item, Data),
+ Item.
+
+create_config_page(#state{sys = Sys, book = Book} = S) ->
+ Panel = wxPanel:new(Book, []),
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+ AppConds = reltool_utils:incl_conds(),
+ AppBox = wxRadioBox:new(Panel,
+ ?wxID_ANY,
+ "Application inclusion policy",
+ ?wxDefaultPosition,
+ ?wxDefaultSize,
+ AppConds,
+ []),
+ AppToolTip = "Choose default policy for inclusion of applications. ",
+ wxBitmapButton:setToolTip(AppBox, AppToolTip),
+ AppChoice = reltool_utils:incl_cond_to_index(Sys#sys.incl_cond),
+ wxRadioBox:setSelection(AppBox, AppChoice),
+ wxEvtHandler:connect(AppBox, command_radiobox_selected,
+ [{userData, config_incl_cond}]),
+ ModConds = reltool_utils:mod_conds(),
+ ModBox = wxRadioBox:new(Panel,
+ ?wxID_ANY,
+ "Module inclusion policy",
+ ?wxDefaultPosition,
+ ?wxDefaultSize,
+ ModConds,
+ []),
+ ModToolTip = "Choose default policy for module inclusion.",
+ wxBitmapButton:setToolTip(ModBox, ModToolTip),
+
+ ModChoice = reltool_utils:mod_cond_to_index(Sys#sys.mod_cond),
+ wxRadioBox:setSelection(ModBox, ModChoice),
+ wxEvtHandler:connect(ModBox, command_radiobox_selected,
+ [{userData, config_mod_cond}]),
+
+ wxSizer:add(Sizer,
+ AppBox,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+ wxSizer:add(Sizer,
+ ModBox,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+ wxPanel:setSizer(Panel, Sizer),
+ wxNotebook:addPage(Book, Panel, ?SYS_PAGE, []),
+ S.
+
+create_main_release_page(#state{book = Book} = S) ->
+ Panel = wxPanel:new(Book, []),
+ RelBook = wxNotebook:new(Panel, ?wxID_ANY, []),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ ButtonSizer = wxBoxSizer:new(?wxHORIZONTAL),
+
+ Create = wxButton:new(Panel, ?wxID_ANY, [{label, "Create"}]),
+ wxButton:setToolTip(Create, "Create a new release."),
+ wxButton:connect(Create, command_button_clicked, [{userData, create_rel}]),
+ wxSizer:add(ButtonSizer, Create),
+
+ Delete = wxButton:new(Panel, ?wxID_ANY, [{label, "Delete"}]),
+ wxButton:setToolTip(Delete, "Delete a release."),
+ wxButton:connect(Delete, command_button_clicked, [{userData, delete_rel}]),
+ wxSizer:add(ButtonSizer, Delete),
+
+ View = wxButton:new(Panel, ?wxID_ANY, [{label, "View script"}]),
+ wxButton:setToolTip(View, "View generated script file."),
+ wxButton:connect(View, command_button_clicked, [{userData, view_script}]),
+ wxSizer:add(ButtonSizer, View),
+
+ [add_release_page(RelBook, Rel) || Rel <- (S#state.sys)#sys.rels],
+
+ wxSizer:add(Sizer, RelBook, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxSizer:add(Sizer, ButtonSizer, [{flag, ?wxEXPAND}]),
+ wxPanel:setSizer(Panel, Sizer),
+ wxNotebook:addPage(Book, Panel, ?REL_PAGE, []),
+ S#state{rel_book = RelBook}.
+
+add_release_page(Book, #rel{name = RelName, rel_apps = RelApps}) ->
+ Panel = wxPanel:new(Book, []),
+ Sizer = wxBoxSizer:new(?wxHORIZONTAL),
+ RelBox = wxRadioBox:new(Panel,
+ ?wxID_ANY,
+ "Applications included in the release " ++ RelName,
+ ?wxDefaultPosition,
+ ?wxDefaultSize,
+ [atom_to_list(RA#rel_app.name) || RA <- RelApps],
+ []),
+ %% wxRadioBox:setSelection(RelBox, 2), % mandatory
+ wxEvtHandler:connect(RelBox, command_radiobox_selected, [{userData, {config_rel_cond, RelName}}]),
+ RelToolTip = "Choose which applications that shall be included in the release resource file.",
+ wxBitmapButton:setToolTip(RelBox, RelToolTip),
+
+ wxSizer:add(Sizer,
+ RelBox,
+ [{border, 2},
+ {flag, ?wxALL bor ?wxEXPAND},
+ {proportion, 1}]),
+ wxPanel:setSizer(Panel, Sizer),
+ wxNotebook:addPage(Book, Panel, RelName, []).
+
+do_open_app(S, AppBase) when is_list(AppBase) ->
+ {AppName, _AppVsn} = reltool_utils:split_app_name(AppBase),
+ do_open_app(S, AppName);
+do_open_app(S, '') ->
+ S;
+do_open_app(#state{server_pid = ServerPid, common = C, app_wins = AppWins} = S, AppName) when is_atom(AppName) ->
+ case lists:keysearch(AppName, #app_win.name, AppWins) of
+ false ->
+ WxEnv = wx:get_env(),
+ {ok, Pid} = reltool_app_win:start_link(WxEnv, ServerPid, C, AppName),
+ AW = #app_win{name = AppName, pid = Pid},
+ S#state{app_wins = [AW | AppWins]};
+ {value, AW} ->
+ reltool_app_win:raise(AW#app_win.pid),
+ S
+ end.
+
+root_popup(S, Root, Tree, Item) ->
+ PopupMenu = wxMenu:new(),
+ wxMenu:append(PopupMenu, 0, "Root dir"),
+ wxMenu:appendSeparator(PopupMenu),
+ wxMenu:append(PopupMenu, 1, "Edit"),
+ Choices = [edit],
+ wxEvtHandler:connect(PopupMenu, command_menu_selected),
+ wxEvtHandler:connect(PopupMenu, menu_close),
+ wxWindow:popupMenu(S#state.frame, PopupMenu),
+
+ Popup = #root_popup{dir = Root, choices = Choices, tree = Tree, item = Item},
+ S#state{popup_menu = Popup}.
+
+lib_popup(S, Lib, Tree, Item) ->
+ PopupMenu = wxMenu:new(),
+ wxMenu:append(PopupMenu, 0, "Library dir"),
+ wxMenu:appendSeparator(PopupMenu),
+ wxMenu:append(PopupMenu, 1, "Add"),
+ Choices =
+ case wxTreeCtrl:getItemData(Tree, Item) of
+ #lib_data{dir = undefined} ->
+ [add];
+ #lib_data{} ->
+ wxMenu:append(PopupMenu, 2, "Edit"),
+ wxMenu:append(PopupMenu, 3, "Delete"),
+ [add, edit, delete]
+ end,
+ wxEvtHandler:connect(PopupMenu, command_menu_selected),
+ wxEvtHandler:connect(PopupMenu, menu_close),
+ wxWindow:popupMenu(S#state.frame, PopupMenu),
+
+ Popup = #lib_popup{dir = Lib, choices = Choices, tree = Tree, item = Item},
+ S#state{popup_menu = Popup}.
+
+escript_popup(S, File, Tree, Item) ->
+ PopupMenu = wxMenu:new(),
+ wxMenu:append(PopupMenu, 0, "Escript file"),
+ wxMenu:appendSeparator(PopupMenu),
+ wxMenu:append(PopupMenu, 1, "Add"),
+ Choices =
+ case wxTreeCtrl:getItemData(Tree, Item) of
+ #escript_data{file = undefined} ->
+ [add];
+ #escript_data{} ->
+ wxMenu:append(PopupMenu, 2, "Edit"),
+ wxMenu:append(PopupMenu, 3, "Delete"),
+ [add, edit, delete]
+ end,
+ wxEvtHandler:connect(PopupMenu, command_menu_selected),
+ wxEvtHandler:connect(PopupMenu, menu_close),
+ wxWindow:popupMenu(S#state.frame, PopupMenu),
+
+ Popup = #escript_popup{file = File, choices = Choices, tree = Tree, item = Item},
+ S#state{popup_menu = Popup}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_event(S, #wx{id = Id, obj= ObjRef, userData = UserData, event = Event} = _Wx) ->
+ %% io:format("wx: ~p\n", [Wx]),
+ case Event of
+ #wxSize{type = size, size = {W, _H}} when UserData =:= app_list_ctrl ->
+ wxListCtrl:setColumnWidth(ObjRef, ?APPS_APP_COL, W),
+ S;
+ #wxCommand{type = command_menu_selected} when Id =:= ?APP_GRAPH_ITEM ->
+ update_app_graph(S);
+ #wxCommand{type = command_menu_selected} when Id =:= ?MOD_GRAPH_ITEM ->
+ update_mod_graph(S);
+ #wxCommand{type = command_menu_selected} when Id =:= ?RESET_CONFIG_ITEM ->
+ reset_config(S);
+ #wxCommand{type = command_menu_selected} when Id =:= ?UNDO_CONFIG_ITEM ->
+ undo_config(S);
+ #wxCommand{type = command_menu_selected} when Id =:= ?LOAD_CONFIG_ITEM ->
+ load_config(S);
+ #wxCommand{type = command_menu_selected} when Id =:= ?SAVE_CONFIG_NODEF_NODER_ITEM ->
+ save_config(S, false, false);
+ #wxCommand{type = command_menu_selected} when Id =:= ?SAVE_CONFIG_NODEF_DER_ITEM ->
+ save_config(S, false, true);
+ #wxCommand{type = command_menu_selected} when Id =:= ?SAVE_CONFIG_DEF_NODER_ITEM ->
+ save_config(S, true, false);
+ #wxCommand{type = command_menu_selected} when Id =:= ?SAVE_CONFIG_DEF_DER_ITEM ->
+ save_config(S, true, true);
+ #wxCommand{type = command_menu_selected} when Id =:= ?GEN_REL_FILES_ITEM ->
+ gen_rel_files(S);
+ #wxCommand{type = command_menu_selected} when Id =:= ?GEN_TARGET_ITEM ->
+ gen_target(S);
+ #wxCommand{type = command_menu_selected} when UserData =:= main_window, Id =:= ?CONTENTS_ITEM ->
+ {file, BeamFile} = code:is_loaded(?MODULE),
+ EbinDir = filename:dirname(BeamFile),
+ AppDir = filename:dirname(EbinDir),
+ HelpFile = filename:join([AppDir, "doc", "html", "index.html"]),
+ Url = "file://" ++ filename:absname(HelpFile),
+ wx_misc:launchDefaultBrowser(Url),
+ S;
+ #wxCommand{type = command_menu_selected} when UserData =:= main_window, Id =:= ?ABOUT_ITEM ->
+ AboutStr = "Reltool is a release management tool. It analyses a given"
+ " Erlang/OTP installation and determines various dependencies"
+ " between applications. The graphical frontend depicts the"
+ " dependencies and enables interactive customization of a"
+ " target system. The backend provides a batch interface"
+ " for generation of customized target systems.",
+ MD = wxMessageDialog:new(S#state.frame,
+ AboutStr,
+ [{style, ?wxOK bor ?wxICON_INFORMATION},
+ {caption, "About Reltool"}]),
+ wxMessageDialog:showModal(MD),
+ wxMessageDialog:destroy(MD),
+ S;
+ #wxMenu{type = menu_close} ->
+ S#state{popup_menu = undefined};
+ #wxCommand{type = command_menu_selected = Type, cmdString = Str}
+ when S#state.popup_menu =/= undefined ->
+ handle_popup_event(S, Type, Id, ObjRef, UserData, Str);
+ #wxMouse{type = enter_window} ->
+ wxWindow:setFocus(ObjRef),
+ S;
+ _ ->
+ case wxNotebook:getPageText(S#state.book, wxNotebook:getSelection(S#state.book)) of
+ ?APP_PAGE -> handle_app_event(S, Event, ObjRef, UserData);
+ ?LIB_PAGE -> handle_source_event(S, Event, ObjRef, UserData);
+ ?SYS_PAGE -> handle_system_event(S, Event, ObjRef, UserData);
+ ?REL_PAGE -> handle_release_event(S, Event, ObjRef, UserData)
+ end
+ end.
+
+handle_popup_event(S, _Type, 0, _ObjRef, _UserData, _Str) ->
+ S#state{popup_menu = undefined};
+handle_popup_event(#state{popup_menu = #root_popup{dir = OldDir, choices = Choices},
+ sys = Sys} = S,
+ _Type, Pos, _ObjRef, _UserData, _Str) ->
+ case lists:nth(Pos, Choices) of
+ edit ->
+ Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST,
+ case select_dir(S#state.frame, "Change root directory", OldDir, Style) of
+ {ok, NewDir} when NewDir =:= OldDir ->
+ %% Same dir.Ignore.
+ S#state{popup_menu = undefined};
+ {ok, NewDir} ->
+ Sys2 = Sys#sys{root_dir = NewDir},
+ do_set_sys(S#state{popup_menu = undefined, sys = Sys2});
+ cancel ->
+ S#state{popup_menu = undefined}
+ end
+ end;
+handle_popup_event(#state{popup_menu = #lib_popup{dir = OldDir, choices = Choices},
+ sys = Sys} = S,
+ _Type, Pos, _ObjRef, _UserData, _Str) ->
+ case lists:nth(Pos, Choices) of
+ add ->
+ {ok, Cwd} = file:get_cwd(),
+ Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST,
+ case select_dir(S#state.frame, "Select a library directory to add", Cwd, Style) of
+ {ok, NewDir} ->
+ case lists:member(NewDir, Sys#sys.lib_dirs) of
+ true ->
+ %% Ignore duplicate. Keep old.
+ S#state{popup_menu = undefined};
+ false ->
+ LibDirs = Sys#sys.lib_dirs ++ [NewDir],
+ Sys2 = Sys#sys{lib_dirs = LibDirs},
+ do_set_sys(S#state{popup_menu = undefined, sys = Sys2})
+ end;
+ cancel ->
+ S#state{popup_menu = undefined}
+ end;
+ edit ->
+ Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST,
+ case select_dir(S#state.frame, "Change library directory", OldDir, Style) of
+ {ok, NewDir} ->
+ case lists:member(NewDir, Sys#sys.lib_dirs) of
+ true ->
+ %% Ignore duplicate. Keep old.
+ S#state{popup_menu = undefined};
+ false ->
+ Pred = fun(E) -> E =/= OldDir end,
+ {Before, [_| After]} =
+ lists:splitwith(Pred, Sys#sys.lib_dirs),
+ LibDirs2 = Before ++ [NewDir | After],
+ Sys2 = Sys#sys{lib_dirs = LibDirs2},
+ do_set_sys(S#state{popup_menu = undefined, sys = Sys2})
+ end;
+ cancel ->
+ S#state{popup_menu = undefined}
+ end;
+ delete ->
+ LibDirs = Sys#sys.lib_dirs -- [OldDir],
+ Sys2 = Sys#sys{lib_dirs = LibDirs},
+ do_set_sys(S#state{popup_menu = undefined, sys = Sys2})
+ end;
+handle_popup_event(#state{popup_menu = #escript_popup{file = OldFile, choices = Choices},
+ sys = Sys} = S,
+ _Type, Pos, _ObjRef, _UserData, _Str) ->
+ case lists:nth(Pos, Choices) of
+ add ->
+ OldFile2 =
+ case OldFile of
+ undefined ->
+ {ok, Cwd} = file:get_cwd(),
+ filename:join([Cwd, "myEscript"]);
+ _ ->
+ OldFile
+ end,
+ Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST,
+ case select_file(S#state.frame, "Select an escript file to add", OldFile2, Style) of
+ {ok, NewFile} ->
+ case lists:member(NewFile, Sys#sys.escripts) of
+ true ->
+ %% Ignore duplicate. Keep old.
+ S#state{popup_menu = undefined};
+ false ->
+ Escripts = Sys#sys.escripts ++ [NewFile],
+ Sys2 = Sys#sys{escripts = Escripts},
+ do_set_sys(S#state{popup_menu = undefined, sys = Sys2})
+ end;
+ cancel ->
+ S#state{popup_menu = undefined}
+ end;
+ edit ->
+ Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST,
+ case select_file(S#state.frame, "Change escript file name", OldFile, Style) of
+ {ok, NewFile} ->
+ case lists:member(NewFile, Sys#sys.escripts) of
+ true ->
+ %% Ignore duplicate. Keep old.
+ S#state{popup_menu = undefined};
+ false ->
+ Pred = fun(E) -> E =/= OldFile end,
+ {Before, [_| After]} = lists:splitwith(Pred, Sys#sys.escripts),
+ Escripts2 = Before ++ [NewFile | After],
+ Sys2 = Sys#sys{escripts = Escripts2},
+ do_set_sys(S#state{popup_menu = undefined, sys = Sys2})
+ end;
+ cancel ->
+ S#state{popup_menu = undefined}
+ end;
+ delete ->
+ Escripts = Sys#sys.escripts -- [OldFile],
+ Sys2 = Sys#sys{escripts = Escripts},
+ do_set_sys(S#state{popup_menu = undefined, sys = Sys2})
+ end.
+
+handle_system_event(#state{sys = Sys} = S,
+ #wxCommand{type = command_radiobox_selected, cmdString = Choice},
+ _ObjRef,
+ config_mod_cond) ->
+ ModCond = reltool_utils:list_to_mod_cond(Choice),
+ Sys2 = Sys#sys{mod_cond = ModCond},
+ do_set_sys(S#state{sys = Sys2});
+handle_system_event(#state{sys = Sys} = S,
+ #wxCommand{type = command_radiobox_selected, cmdString = Choice},
+ _ObjRef,
+ config_incl_cond) ->
+ AppCond = reltool_utils:list_to_incl_cond(Choice),
+ Sys2 = Sys#sys{incl_cond = AppCond},
+ do_set_sys(S#state{sys = Sys2});
+handle_system_event(S, Event, ObjRef, UserData) ->
+ error_logger:format("~p~p got unexpected wx sys event to ~p with user data: ~p\n\t ~p\n",
+ [?MODULE, self(), ObjRef, UserData, Event]),
+ S.
+
+handle_release_event(S, _Event, _ObjRef, UserData) ->
+ io:format("Release data: ~p\n", [UserData]),
+ S.
+
+handle_source_event(S, #wxTree{type = command_tree_item_activated, item = Item}, ObjRef, _UserData) ->
+ case wxTreeCtrl:getItemData(ObjRef, Item) of
+ #root_data{dir = _Dir} ->
+ %% io:format("Root dialog: ~p\n", [Dir]),
+ S;
+ #lib_data{dir = _Dir} ->
+ %% io:format("Lib dialog: ~p\n", [Dir]),
+ S;
+ #escript_data{file = _File} ->
+ %% io:format("Escript dialog: ~p\n", [File]),
+ S;
+ #app_data{name = Name} ->
+ do_open_app(S, Name);
+ undefined ->
+ S
+ end;
+handle_source_event(S, #wxTree{type = command_tree_item_right_click, item = Item}, Tree, _UserData) ->
+ case wxTreeCtrl:getItemData(Tree, Item) of
+ #root_data{dir = Dir} ->
+ wx:batch(fun() -> root_popup(S, Dir, Tree, Item) end);
+ #lib_data{dir = Dir} ->
+ wx:batch(fun() -> lib_popup(S, Dir, Tree, Item) end);
+ #escript_data{file = File} ->
+ wx:batch(fun() -> escript_popup(S, File, Tree, Item) end);
+ #app_data{name = Name} ->
+ io:format("App menu: ~p\n", [Name]),
+ S;
+ undefined ->
+ S
+ end.
+
+handle_app_event(S, #wxList{type = command_list_item_activated, itemIndex = Pos}, ListCtrl, _UserData) ->
+ AppName = wxListCtrl:getItemText(ListCtrl, Pos),
+ do_open_app(S, AppName);
+handle_app_event(S, #wxCommand{type = command_button_clicked}, _ObjRef, {app_button, Action, ListCtrl}) ->
+ Items = reltool_utils:get_items(ListCtrl),
+ handle_app_button(S, Items, Action);
+handle_app_event(S, Event, ObjRef, UserData) ->
+ error_logger:format("~p~p got unexpected wx app event to ~p with user data: ~p\n\t ~p\n",
+ [?MODULE, self(), ObjRef, UserData, Event]),
+ S.
+
+handle_app_button(#state{server_pid = ServerPid, app_wins = AppWins} = S, Items, Action) ->
+ NewApps = [move_app(S, Item, Action) || Item <- Items],
+ case reltool_server:set_apps(ServerPid, NewApps) of
+ {ok, []} ->
+ ok;
+ {ok, Warnings} ->
+ Msg = lists:flatten([[W, $\n] || W <- Warnings]),
+ display_message(Msg, ?wxICON_WARNING);
+ {error, Reason} ->
+ display_message(Reason, ?wxICON_ERROR)
+ end,
+ [ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- AppWins],
+ redraw_apps(S).
+
+do_set_sys(#state{sys = Sys, server_pid = ServerPid, status_bar = Bar} = S) ->
+ wxStatusBar:setStatusText(Bar, "Processing libraries..."),
+ Status = reltool_server:set_sys(ServerPid, Sys),
+ check_and_refresh(S, Status).
+
+move_app(S, {_ItemNo, AppBase}, Action) ->
+ {AppName, _Vsn} = reltool_utils:split_app_name(AppBase),
+ {ok, OldApp} = reltool_server:get_app(S#state.server_pid, AppName),
+ AppCond =
+ case Action of
+ whitelist_add ->
+ case OldApp#app.incl_cond of
+ include -> undefined;
+ exclude -> include;
+ undefined -> include
+ end;
+ whitelist_del ->
+ undefined;
+ blacklist_add ->
+ exclude;
+ blacklist_del ->
+ undefined;
+ _ ->
+ error_logger:format("~p~p got unexpected app button event: ~p ~p\n",
+ [?MODULE, self(), Action, AppBase]),
+ OldApp#app.incl_cond
+ end,
+ OldApp#app{incl_cond = AppCond}.
+
+do_set_app(#state{server_pid = ServerPid, app_wins = AppWins} = S, NewApp) ->
+ {ok, AnalysedApp, Warnings} = reltool_server:set_app(ServerPid, NewApp),
+ [ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- AppWins],
+ S2 = redraw_apps(S),
+ case Warnings of
+ [] ->
+ ignore;
+ _ ->
+ Msg = lists:flatten([[W, $\n] || W <- Warnings]),
+ display_message(Msg, ?wxICON_WARNING)
+ end,
+ {ok, AnalysedApp, S2}.
+
+redraw_apps(#state{server_pid = ServerPid,
+ source = SourceCtrl,
+ whitelist = WhiteCtrl,
+ blacklist = BlackCtrl,
+ derived = DerivedCtrl} = S) ->
+ {ok, SourceApps} = reltool_server:get_apps(ServerPid, source),
+ {ok, WhiteApps} = reltool_server:get_apps(ServerPid, whitelist),
+ {ok, BlackApps} = reltool_server:get_apps(ServerPid, blacklist),
+ {ok, DerivedApps} = reltool_server:get_apps(ServerPid, derived),
+
+ BadApps = fun(#app{used_by_apps = UsedBy} = A) when UsedBy =/= [] ->
+ A#app{status = missing};
+ (A) ->
+ A
+ end,
+ BlackApps2 = lists:map(BadApps, BlackApps),
+ redraw_apps(SourceApps, SourceCtrl, ?CROSS_IMAGE, ?WARN_IMAGE),
+ WhiteN = redraw_apps(WhiteApps, WhiteCtrl, ?TICK_IMAGE, ?ERR_IMAGE),
+ redraw_apps(BlackApps2, BlackCtrl, ?CROSS_IMAGE, ?WARN_IMAGE),
+ DerivedN = redraw_apps(DerivedApps, DerivedCtrl, ?TICK_IMAGE, ?ERR_IMAGE),
+ Status = lists:concat([WhiteN, " whitelisted modules and ",
+ DerivedN, " derived modules."]),
+ wxStatusBar:setStatusText(S#state.status_bar, Status),
+ S.
+
+redraw_apps(Apps, ListCtrl, OkImage, ErrImage) ->
+ do_redraw_apps(ListCtrl, Apps, OkImage, ErrImage).
+
+do_redraw_apps(ListCtrl, [], _OkImage, _ErrImage) ->
+ wxListCtrl:deleteAllItems(ListCtrl),
+ 0;
+do_redraw_apps(ListCtrl, Apps, OkImage, ErrImage) ->
+ OldItems = reltool_utils:get_items(ListCtrl),
+ wxListCtrl:deleteAllItems(ListCtrl),
+ AddImage =
+ fun(App) ->
+ case App#app.status of
+ ok -> {OkImage, App#app.label, App};
+ missing -> {ErrImage, App#app.label, App}
+ end
+ end,
+ ImageApps = lists:map(AddImage, Apps),
+ Show =
+ fun({ImageId, Text, App}, {Row, ModCount, Items}) ->
+ wxListCtrl:insertItem(ListCtrl, Row, ""),
+ if (Row rem 2) =:= 0 ->
+ wxListCtrl:setItemBackgroundColour(ListCtrl, Row, {240,240,255});
+ true ->
+ ignore
+ end,
+ wxListCtrl:setItem(ListCtrl, Row, ?APPS_APP_COL, Text, [{imageId, ImageId}]),
+ N = length([M || M <- App#app.mods, M#mod.is_included =:= true]),
+ {Row + 1, ModCount + N, [{Row, Text} | Items]}
+ end,
+ {_, N, NewItems} = wx:foldl(Show, {0, 0, []}, lists:sort(ImageApps)),
+ reltool_utils:select_items(ListCtrl, OldItems, lists:reverse(NewItems)),
+ N.
+
+update_app_graph(S) ->
+ {ok, WhiteApps} = reltool_server:get_apps(S#state.server_pid, whitelist),
+ {ok, DerivedApps} = reltool_server:get_apps(S#state.server_pid, derived),
+
+ WhiteNames = [A#app.name || A <- WhiteApps],
+ DerivedNames = [A#app.name || A <- DerivedApps],
+ Nodes = WhiteNames ++ DerivedNames,
+ %% WhiteUses = [N || A <- WhiteApps, N <- A#app.uses_apps, lists:member(N, Nodes)],
+ %% DerivedUses = [N || A <- DerivedApps, N <- A#app.uses_apps, lists:member(N, Nodes)],
+
+ WhiteLinks = [[A#app.name, U] || A <- WhiteApps,
+ U <- A#app.uses_apps,
+ U =/= A#app.name,
+ lists:member(U, Nodes)],
+ DerivedLinks = [[A#app.name, U] || A <- DerivedApps,
+ U <- A#app.uses_apps,
+ U =/= A#app.name,
+ lists:member(U, Nodes)],
+ Links = lists:usort(WhiteLinks ++ DerivedLinks),
+ %% io:format("Links: ~p\n", [Links]),
+ Title = lists:concat([?APPLICATION, " - application graph"]),
+ create_fgraph_window(S, Title, Nodes, Links).
+
+update_mod_graph(S) ->
+ {ok, WhiteApps} = reltool_server:get_apps(S#state.server_pid, whitelist),
+ {ok, DerivedApps} = reltool_server:get_apps(S#state.server_pid, derived),
+ WhiteMods = lists:usort([M || A <- WhiteApps, M <- A#app.mods, M#mod.is_included =:= true]),
+ DerivedMods = lists:usort([M || A <- DerivedApps, M <- A#app.mods, M#mod.is_included =:= true]),
+
+ WhiteNames = [M#mod.name || M <- WhiteMods],
+ DerivedNames = [M#mod.name || M <- DerivedMods],
+ Nodes = WhiteNames ++ DerivedNames,
+
+ WhiteLinks = [[M#mod.name, U] || M <- WhiteMods,
+ U <- M#mod.uses_mods,
+ U =/= M#mod.name,
+ lists:member(U, Nodes)],
+ DerivedLinks = [[M#mod.name, U] || M <- DerivedMods,
+ U <- M#mod.uses_mods,
+ U =/= M#mod.name,
+ lists:member(U, Nodes)],
+ Links = lists:usort(WhiteLinks ++ DerivedLinks),
+ %% io:format("Links: ~p\n", [Links]),
+ Title = lists:concat([?APPLICATION, " - module graph"]),
+ create_fgraph_window(S, Title, Nodes, Links).
+
+create_fgraph_window(S, Title, Nodes, Links) ->
+ Frame = wxFrame:new(wx:null(), ?wxID_ANY, Title, []),
+ wxFrame:setSize(Frame, {?WIN_WIDTH, ?WIN_HEIGHT}),
+ Panel = wxPanel:new(Frame, []),
+ Options = [{size, {lists:max([100, ?WIN_WIDTH - 100]), ?WIN_HEIGHT}}],
+ {Server, Fgraph} = reltool_fgraph_win:new(Panel, Options),
+ Choose = fun(?MISSING_APP) -> alternate;
+ (_) -> default
+ end,
+ [reltool_fgraph_win:add_node(Server, N, Choose(N)) || N <- Nodes],
+ [reltool_fgraph_win:add_link(Server, {From, To}) || [From, To] <- Links],
+
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Sizer, Fgraph, [{flag, ?wxEXPAND}, {proportion, 1}]),
+ wxPanel:setSizer(Panel, Sizer),
+ %% wxSizer:fit(Sizer, Frame),
+ %% wxSizer:setSizeHints(Sizer, Frame),
+ wxFrame:connect(Frame, close_window),
+ wxFrame:show(Frame),
+ FW = #fgraph_win{frame = Frame, pid = Server},
+ S#state{fgraph_wins = [FW | S#state.fgraph_wins]}.
+
+reset_config(#state{status_bar = Bar} = S) ->
+ wxStatusBar:setStatusText(Bar, "Processing libraries..."),
+ Status = reltool_server:reset_config(S#state.server_pid),
+ check_and_refresh(S, Status).
+
+undo_config(#state{status_bar = Bar} = S) ->
+ wxStatusBar:setStatusText(Bar, "Processing libraries..."),
+ ok = reltool_server:undo_config(S#state.server_pid),
+ refresh(S).
+
+load_config(#state{status_bar = Bar, config_file = OldFile} = S) ->
+ Style = ?wxFD_OPEN bor ?wxFD_FILE_MUST_EXIST,
+ case select_file(S#state.frame, "Select a file to load the configuration from", OldFile, Style) of
+ {ok, NewFile} ->
+ wxStatusBar:setStatusText(Bar, "Processing libraries..."),
+ Status = reltool_server:load_config(S#state.server_pid, NewFile),
+ check_and_refresh(S#state{config_file = NewFile}, Status);
+ cancel ->
+ S
+ end.
+
+save_config(#state{config_file = OldFile} = S, InclDefaults, InclDerivates) ->
+ Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT,
+ case select_file(S#state.frame, "Select a file to save the configuration to", OldFile, Style) of
+ {ok, NewFile} ->
+ Status = reltool_server:save_config(S#state.server_pid, NewFile, InclDefaults, InclDerivates),
+ check_and_refresh(S#state{config_file = NewFile}, Status);
+ cancel ->
+ S
+ end.
+
+gen_rel_files(#state{target_dir = OldDir} = S) ->
+ Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT,
+ case select_dir(S#state.frame, "Select a directory to generate rel, script and boot files to", OldDir, Style) of
+ {ok, NewDir} ->
+ Status = reltool_server:gen_rel_files(S#state.server_pid, NewDir),
+ check_and_refresh(S, Status);
+ cancel ->
+ S
+ end.
+
+gen_target(#state{target_dir = OldDir} = S) ->
+ Style = ?wxFD_SAVE bor ?wxFD_OVERWRITE_PROMPT,
+ case select_dir(S#state.frame, "Select a directory to generate a target system to", OldDir, Style) of
+ {ok, NewDir} ->
+ Status = reltool_server:gen_target(S#state.server_pid, NewDir),
+ check_and_refresh(S#state{target_dir = NewDir}, Status);
+ cancel ->
+ S
+ end.
+
+select_file(Frame, Message, DefaultFile, Style) ->
+ Dialog = wxFileDialog:new(Frame,
+ [{message, Message},
+ {defaultDir, filename:dirname(DefaultFile)},
+ {defaultFile, filename:basename(DefaultFile)},
+ {style, Style}]),
+ Choice =
+ case wxMessageDialog:showModal(Dialog) of
+ ?wxID_CANCEL -> cancel;
+ ?wxID_OK -> {ok, wxFileDialog:getPath(Dialog)}
+ end,
+ wxFileDialog:destroy(Dialog),
+ Choice.
+
+select_dir(Frame, Message, DefaultDir, Style) ->
+ Dialog = wxDirDialog:new(Frame,
+ [{title, Message},
+ {defaultPath, DefaultDir},
+ {style, Style}]),
+ Choice =
+ case wxMessageDialog:showModal(Dialog) of
+ ?wxID_CANCEL -> cancel;
+ ?wxID_OK -> {ok, wxDirDialog:getPath(Dialog)}
+ end,
+ wxDirDialog:destroy(Dialog),
+ Choice.
+
+check_and_refresh(S, Status) ->
+ case Status of
+ ok ->
+ true;
+ {ok, Warnings} ->
+ undo_dialog(S, Warnings);
+ {error, Reason} when is_list(Reason) ->
+ display_message(Reason, ?wxICON_ERROR),
+ false;
+ {error, Reason} ->
+ Msg = lists:flatten(io_lib:format("Error:\n\n~p\n", [Reason])),
+ display_message(Msg, ?wxICON_ERROR),
+ false
+ end,
+ refresh(S).
+
+refresh(S) ->
+ {ok, Sys} = reltool_server:get_sys(S#state.server_pid),
+ [ok = reltool_app_win:refresh(AW#app_win.pid) || AW <- S#state.app_wins],
+ S2 = S#state{sys = Sys},
+ S3 = redraw_libs(S2),
+ redraw_apps(S3).
+
+question_dialog(Question, Details) ->
+ %% Parent = S#state.frame,
+ Parent = wx:typeCast(wx:null(), wxWindow),
+ %% [{style, ?wxYES_NO bor ?wxICON_ERROR bor ?wx}]),
+ DialogStyle = ?wxRESIZE_BORDER bor ?wxCAPTION bor ?wxSYSTEM_MENU bor
+ ?wxMINIMIZE_BOX bor ?wxMAXIMIZE_BOX bor ?wxCLOSE_BOX,
+ Dialog = wxDialog:new(Parent, ?wxID_ANY, "Undo dialog", [{style, DialogStyle}]),
+ Color = wxWindow:getBackgroundColour(Dialog),
+ TextStyle = ?wxTE_READONLY bor ?wxTE_MULTILINE bor ?wxHSCROLL,
+ Text1 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{style, ?wxTE_READONLY bor ?wxBORDER_NONE}]),
+ wxWindow:setBackgroundColour(Text1, Color),
+ wxTextCtrl:appendText(Text1, Question),
+ Text2 = wxTextCtrl:new(Dialog, ?wxID_ANY, [{size, {600, 400}}, {style, TextStyle}]),
+ wxWindow:setBackgroundColour(Text2, Color),
+ wxTextCtrl:appendText(Text2, Details),
+ %% wxDialog:setAffirmativeId(Dialog, ?wxID_YES),
+ %% wxDialog:setEscapeId(Dialog, ?wxID_NO),
+ Sizer = wxBoxSizer:new(?wxVERTICAL),
+ wxSizer:add(Sizer, Text1, [{border, 2}, {flag, ?wxEXPAND}]),
+ wxSizer:add(Sizer, Text2, [{border, 2}, {flag, ?wxEXPAND}, {proportion, 1}]),
+ ButtSizer = wxDialog:createStdDialogButtonSizer(Dialog, ?wxOK bor ?wxCANCEL),
+ wxSizer:add(Sizer, ButtSizer, [{border, 2}, {flag, ?wxEXPAND}]),
+ wxPanel:setSizer(Dialog, Sizer),
+ wxSizer:fit(Sizer, Dialog),
+ wxSizer:setSizeHints(Sizer, Dialog),
+ Answer = wxDialog:showModal(Dialog),
+ wxDialog:destroy(Dialog),
+ Answer.
+
+undo_dialog(_S, []) ->
+ true;
+undo_dialog(S, Warnings) ->
+ Question = "Do you want to perform the update despite these warnings?",
+ Details = lists:flatten([[W, $\n] || W <- Warnings]),
+ case question_dialog(Question, Details) of
+ ?wxID_OK ->
+ true;
+ ?wxID_CANCEL ->
+ reltool_server:undo_config(S#state.server_pid),
+ false
+ end.
+
+display_message(Message, Icon) ->
+ Dialog = wxMessageDialog:new(wx:null(),
+ Message,
+ [{style, ?wxOK bor Icon}]),
+ wxMessageDialog:showModal(Dialog),
+ wxMessageDialog:destroy(Dialog).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% sys callbacks
+
+system_continue(_Parent, _Debug, S) ->
+ ?MODULE:loop(S).
+
+system_terminate(Reason, _Parent, _Debug, _S) ->
+ exit(Reason).
+
+system_code_change(S,_Module,_OldVsn,_Extra) ->
+ {ok, S}.
diff --git a/lib/reltool/src/reltool_target.erl b/lib/reltool/src/reltool_target.erl
new file mode 100644
index 0000000000..895fc6702b
--- /dev/null
+++ b/lib/reltool/src/reltool_target.erl
@@ -0,0 +1,1226 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(reltool_target).
+
+%% Public
+-export([
+ gen_config/2,
+ gen_app/1,
+ gen_rel/2,
+ gen_rel_files/2,
+ gen_boot/1,
+ gen_script/4,
+ gen_spec/1,
+ eval_spec/3,
+ gen_target/2,
+ install/2
+ ]).
+-compile(export_all).
+-include("reltool.hrl").
+-include_lib("kernel/include/file.hrl").
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Hardcoded internals about the kernel application
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Mandatory modules are modules that must be loaded before processes
+%% can be started. These are a collection of modules from the kernel
+%% and stdlib applications. Nowadays, error_handler dynamically loads
+%% almost every module. The error_handler self must still be there
+%% though.
+
+mandatory_modules() ->
+ [error_handler].
+
+%% Kernel processes are specially treated by the init process. If a
+%% kernel process terminates the whole system terminates.
+
+kernel_processes(KernelApp) ->
+ [
+ {kernelProcess, heart, {heart, start, []}},
+ {kernelProcess, error_logger , {error_logger, start_link, []}},
+ {kernelProcess, application_controller, {application_controller, start, [KernelApp]}}
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Generate the contents of a config file
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+gen_config(#sys{root_dir = RootDir,
+ lib_dirs = LibDirs,
+ mod_cond = ModCond,
+ incl_cond = AppCond,
+ apps = Apps,
+ boot_rel = BootRel,
+ rels = Rels,
+ emu_name = EmuName,
+ profile = Profile,
+ incl_sys_filters = InclSysFiles,
+ excl_sys_filters = ExclSysFiles,
+ incl_app_filters = InclAppFiles,
+ excl_app_filters = ExclAppFiles,
+ incl_archive_filters = InclArchiveDirs,
+ excl_archive_filters = ExclArchiveDirs,
+ archive_opts = ArchiveOpts,
+ relocatable = Relocatable,
+ app_type = AppType,
+ app_file = AppFile,
+ debug_info = DebugInfo},
+ InclDefaults) ->
+ ErtsItems =
+ case lists:keysearch(erts, #app.name, Apps) of
+ {value, Erts} ->
+ [{erts, gen_config(Erts, InclDefaults)}];
+ false ->
+ []
+ end,
+ AppsItems =
+ [{app, A#app.name, gen_config(A, InclDefaults)}
+ || A <- Apps,
+ A#app.name =/= ?MISSING_APP,
+ A#app.name =/= erts,
+ A#app.is_included =:= true,
+ A#app.is_escript =/= true],
+ EscriptItems = [{escript, A#app.active_dir, emit(incl_cond, A#app.incl_cond, undefined, InclDefaults)}
+ || A <- Apps, A#app.is_escript],
+ DefaultRels = reltool_utils:default_rels(),
+ RelsItems =
+ case {[{rel, R#rel.name, R#rel.vsn, gen_config(R, InclDefaults)} || R <- Rels],
+ [{rel, R#rel.name, R#rel.vsn, gen_config(R, InclDefaults)} || R <- DefaultRels]} of
+ {RI, RI} -> [];
+ {RI, _} -> RI
+ end,
+ X = fun(List) -> [Re || #regexp{source = Re} <- List] end,
+ {sys,
+ emit(root_dir, RootDir, code:root_dir(), InclDefaults) ++
+ emit(lib_dirs, LibDirs, ?DEFAULT_LIBS, InclDefaults) ++
+ EscriptItems ++
+ emit(mod_cond, ModCond, ?DEFAULT_MOD_COND, InclDefaults) ++
+ emit(incl_cond, AppCond, ?DEFAULT_INCL_COND, InclDefaults) ++
+ ErtsItems ++
+ AppsItems ++
+ emit(boot_rel, BootRel, ?DEFAULT_REL_NAME, InclDefaults) ++
+ RelsItems ++
+ emit(emu_name, EmuName, ?DEFAULT_EMU_NAME, InclDefaults) ++
+ emit(relocatable, Relocatable, ?DEFAULT_RELOCATABLE, InclDefaults) ++
+ emit(profile, Profile, ?DEFAULT_PROFILE, InclDefaults) ++
+ emit(incl_sys_filters, X(InclSysFiles), ?DEFAULT_INCL_SYS_FILTERS, InclDefaults) ++
+ emit(excl_sys_filters, X(ExclSysFiles), ?DEFAULT_EXCL_SYS_FILTERS, InclDefaults) ++
+ emit(incl_app_filters, X(InclAppFiles), ?DEFAULT_INCL_APP_FILTERS, InclDefaults) ++
+ emit(excl_app_filters, X(ExclAppFiles), ?DEFAULT_EXCL_APP_FILTERS, InclDefaults) ++
+ emit(incl_archive_filters, X(InclArchiveDirs), ?DEFAULT_INCL_ARCHIVE_FILTERS, InclDefaults) ++
+ emit(excl_archive_filters, X(ExclArchiveDirs), ?DEFAULT_EXCL_ARCHIVE_FILTERS, InclDefaults) ++
+ emit(archive_opts, ArchiveOpts, ?DEFAULT_ARCHIVE_OPTS, InclDefaults) ++
+ emit(app_type, AppType, ?DEFAULT_APP_TYPE, InclDefaults) ++
+ emit(app_file, AppFile, ?DEFAULT_APP_FILE, InclDefaults) ++
+ emit(debug_info, DebugInfo, ?DEFAULT_DEBUG_INFO, InclDefaults)};
+gen_config(#app{name = _Name,
+ mod_cond = ModCond,
+ incl_cond = AppCond,
+ debug_info = DebugInfo,
+ app_file = AppFile,
+ incl_app_filters = InclAppFiles,
+ excl_app_filters = ExclAppFiles,
+ incl_archive_filters = InclArchiveDirs,
+ excl_archive_filters = ExclArchiveDirs,
+ archive_opts = ArchiveOpts,
+ use_selected_vsn = UseSelected,
+ vsn = Vsn,
+ mods = Mods},
+ InclDefaults) ->
+ emit(mod_cond, ModCond, undefined, InclDefaults) ++
+ emit(incl_cond, AppCond, undefined, InclDefaults) ++
+ emit(debug_info, DebugInfo, undefined, InclDefaults) ++
+ emit(app_file, AppFile, undefined, InclDefaults) ++
+ emit(incl_app_filters, InclAppFiles, undefined, InclDefaults) ++
+ emit(excl_app_filters, ExclAppFiles, undefined, InclDefaults) ++
+ emit(incl_archive_filters, InclArchiveDirs, undefined, InclDefaults) ++
+ emit(excl_archive_filters, ExclArchiveDirs, undefined, InclDefaults) ++
+ emit(archive_opts, ArchiveOpts, undefined, InclDefaults) ++
+ emit(vsn, Vsn, undefined, InclDefaults orelse UseSelected =/= true) ++
+ [{mod, M#mod.name, gen_config(M, InclDefaults)} || M <- Mods, M#mod.is_included =:= true];
+gen_config(#mod{name = _Name,
+ incl_cond = AppCond,
+ debug_info = DebugInfo},
+ InclDefaults) ->
+ emit(incl_cond, AppCond, undefined, InclDefaults) ++
+ emit(debug_info, DebugInfo, undefined, InclDefaults);
+gen_config(#rel{name = _Name,
+ vsn = _Vsn,
+ rel_apps = RelApps},
+ InclDefaults) ->
+ [gen_config(RA, InclDefaults) || RA <- RelApps];
+gen_config(#rel_app{name = Name,
+ app_type = Type,
+ incl_apps = InclApps},
+ _InclDefaults) ->
+ case {Type, InclApps} of
+ {undefined, []} -> Name;
+ {undefined, _} -> {Name, InclApps};
+ {_, []} -> {Name, Type};
+ {_, _} -> {Name, Type, InclApps}
+ end;
+gen_config({Tag, Val}, InclDefaults) ->
+ emit(Tag, Val, undefined, InclDefaults);
+gen_config([], _InclDefaults) ->
+ [];
+gen_config([H | T], InclDefaults) ->
+ lists:flatten([gen_config(H, InclDefaults), gen_config(T, InclDefaults)]).
+
+emit(Tag, Val, Default, InclDefaults) ->
+ if
+ Val == undefined -> [];
+ InclDefaults -> [{Tag, Val}];
+ Val =/= Default -> [{Tag, Val}];
+ true -> []
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Generate the contents of an app file
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+gen_app(#app{name = Name,
+ info = #app_info{description = Desc,
+ id = Id,
+ vsn = Vsn,
+ modules = Mods,
+ maxP = MaxP,
+ maxT = MaxT,
+ registered = Regs,
+ incl_apps = InclApps,
+ applications = ReqApps,
+ env = Env,
+ mod = StartMod,
+ start_phases = StartPhases}}) ->
+ StartMod2 =
+ case StartMod =:= undefined of
+ true -> [];
+ false -> [{mod, StartMod}]
+ end,
+ {application, Name,
+ [{description, Desc},
+ {vsn, Vsn},
+ {id, Id},
+ {modules, Mods},
+ {registered, Regs},
+ {applications, ReqApps},
+ {included_applications, InclApps},
+ {env, Env},
+ {start_phases, StartPhases},
+ {maxT, MaxT},
+ {maxP, MaxP} |
+ StartMod2]}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Generate the contents of a rel file
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+gen_rel(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps},
+ #sys{apps = Apps}) ->
+ {value, Erts} = lists:keysearch(erts, #app.name, Apps),
+ {release,
+ {RelName, RelVsn},
+ {erts, Erts#app.vsn},
+ [app_to_rel(RA, Apps ) || RA <- RelApps]}.
+
+app_to_rel(#rel_app{name = Name, app_type = Type, incl_apps = InclApps}, Apps) ->
+ {value, #app{vsn = Vsn}} = lists:keysearch(Name, #app.name, Apps),
+ case {Type, InclApps} of
+ {undefined, []} -> {Name, Vsn};
+ {undefined, _} -> {Name, Vsn, InclApps};
+ {_, []} -> {Name, Vsn, Type};
+ {_, _} -> {Name, Vsn, Type, InclApps}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Generate the contents of a boot file
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+gen_boot({script, {_, _}, _} = Script) ->
+ {ok, term_to_binary(Script)}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Generate the contents of a script file
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+gen_script(Rel, Sys, PathFlag, Variables) ->
+ try
+ do_gen_script(Rel, Sys, PathFlag, Variables)
+ catch
+ throw:{error, Text} ->
+ {error, Text}
+ end.
+
+do_gen_script(#rel{name = RelName, vsn = RelVsn, rel_apps = RelApps},
+ #sys{apps = Apps, app_type = DefaultType},
+ PathFlag,
+ Variables) ->
+ {value, Erts} = lists:keysearch(erts, #app.name, Apps),
+ Preloaded = [Mod#mod.name || Mod <- Erts#app.mods],
+ Mandatory = mandatory_modules(),
+ Early = Mandatory ++ Preloaded,
+ MergedApps = [merge_app(RA, Apps, DefaultType) || RA <- RelApps],
+ SortedApps = sort_apps(MergedApps),
+ {value, KernelApp} = lists:keysearch(kernel, #app.name, SortedApps),
+
+ InclApps = lists:append([I || #app{info = #app_info{incl_apps = I}} <- SortedApps]),
+
+ %% Create the script
+ DeepList =
+ [
+ %% Register preloaded modules
+ {preLoaded, lists:sort(Preloaded)},
+ {progress, preloaded},
+
+ %% Load mandatory modules
+ {path, create_mandatory_path(SortedApps, PathFlag, Variables)},
+ {primLoad, lists:sort(Mandatory)},
+ {kernel_load_completed},
+ {progress, kernel_load_completed},
+
+ %% Load remaining modules
+ [load_app_mods(A, Early, PathFlag, Variables) || A <- SortedApps],
+ {progress, modules_loaded},
+
+ %% Start kernel processes
+ {path, create_path(SortedApps, PathFlag, Variables)},
+ kernel_processes(gen_app(KernelApp)),
+ {progress, init_kernel_started},
+
+ %% Load applications
+ [{apply, {application, load, [gen_app(A)]}} ||
+ A = #app{name = Name, app_type = Type} <- SortedApps,
+ Name =/= kernel,
+ Type =/= none],
+ {progress, applications_loaded},
+
+ %% Start applications
+ [{apply, {application, start_boot, [Name, Type]}} ||
+ #app{name = Name, app_type = Type} <- SortedApps,
+ Type =/= none,
+ Type =/= load,
+ not lists:member(Name, InclApps)],
+
+ %% Apply user specific customizations
+ {apply, {c, erlangrc, []}},
+ {progress, started}
+ ],
+ {ok, {script, {RelName, RelVsn}, lists:flatten(DeepList)}}.
+
+merge_app(#rel_app{name = Name, app_type = Type, incl_apps = RelIncl}, Apps, DefaultType) ->
+ {value, App} = lists:keysearch(Name, #app.name, Apps),
+ Type2 =
+ case {Type, App#app.app_type} of
+ {undefined, undefined} -> DefaultType;
+ {undefined, AppType} -> AppType;
+ {_, _} -> Type
+ end,
+ Info = App#app.info,
+ case RelIncl -- Info#app_info.incl_apps of
+ [] ->
+ App#app{app_type = Type2, info = Info#app_info{incl_apps = RelIncl}};
+ BadIncl ->
+ reltool_utils:throw_error("~p: These applications are missing as "
+ "included_applications in the app file: ~p\n",
+ [Name, BadIncl])
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+load_app_mods(#app{mods = Mods} = App, Mand, PathFlag, Variables) ->
+ Path = cr_path(App, PathFlag, Variables),
+ PartNames =
+ lists:sort([{packages:split(M),M} ||
+ #mod{name = M} <- Mods,
+ not lists:member(M, Mand)]),
+ SplitMods =
+ lists:foldl(
+ fun({Parts,M}, [{Last, Acc}|Rest]) ->
+ [_|Tail] = lists:reverse(Parts),
+ case lists:reverse(Tail) of
+ Subs when Subs == Last ->
+ [{Last,[M|Acc]}|Rest];
+ Subs ->
+ [{Subs, [M]}|[{Last,Acc}|Rest]]
+ end
+ end,
+ [{[],
+ []}],
+ PartNames),
+ lists:foldl(
+ fun({Subs,Ms}, Cmds) ->
+ [{path, [filename:join([Path | Subs])]},
+ {primLoad, lists:sort(Ms)} | Cmds]
+ end,
+ [],
+ SplitMods).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Function: sort_apps(Apps) -> {ok, Apps'} | throw({error, Error})
+%% Types: Apps = {{Name, Vsn}, #application}]
+%% Purpose: Sort applications according to dependencies among
+%% applications. If order doesn't matter, use the same
+%% order as in the original list.
+%% Alg. written by Ulf Wiger 970917 ([email protected])
+%% Mod. by mbj
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+sort_apps(Apps) ->
+ sort_apps(Apps, [], [], []).
+
+sort_apps([#app{name = Name, info = Info} = App | Apps], Missing, Circular, Visited) ->
+ {Uses, Apps1, NotFnd1} = find_all(Name, Info#app_info.applications, Apps, Visited, [], []),
+ {Incs, Apps2, NotFnd2} = find_all(Name, lists:reverse(Info#app_info.incl_apps),
+ Apps1, Visited, [], []),
+
+ Missing1 = NotFnd1 ++ NotFnd2 ++ Missing,
+ case Uses ++ Incs of
+ [] ->
+ %% No more app that must be started before this one is
+ %% found; they are all already taken care of (and present
+ %% in Visited list)
+ [App | sort_apps(Apps, Missing1, Circular, [Name | Visited])];
+ L ->
+ %% The apps in L must be started before the app.
+ %% Check if we have already taken care of some app in L,
+ %% in that case we have a circular dependency.
+ NewCircular = [N1 || N1 <- L, N2 <- Visited, N1 =:= N2],
+ Circular1 = case NewCircular of
+ [] -> Circular;
+ _ -> [Name | NewCircular] ++ Circular
+ end,
+ %% L must be started before N, try again, with all apps
+ %% in L added before N.
+ Apps3 = del_apps(NewCircular, L ++ [App | Apps2]),
+ sort_apps(Apps3, Missing1, Circular1, [Name | Visited])
+ end;
+sort_apps([], [], [], _) ->
+ [];
+sort_apps([], Missing, [], _) ->
+ %% this has already been checked before, but as we have the info...
+ reltool_utils:throw_error("Undefined applications: ~p\n", [make_set(Missing)]);
+sort_apps([], [], Circular, _) ->
+ reltool_utils:throw_error("Circular dependencies: ~p\n", [make_set(Circular)]);
+sort_apps([], Missing, Circular, _) ->
+ reltool_utils:throw_error("Circular dependencies: ~p\n"
+ "Undefined applications: ~p\n",
+ [make_set(Circular), make_set(Missing)]).
+
+find_all(CheckingApp, [Name | Names], Apps, Visited, Found, NotFound) ->
+ case lists:keysearch(Name, #app.name, Apps) of
+ {value, #app{info = Info} = App} ->
+ %% It is OK to have a dependecy like
+ %% X includes Y, Y uses X.
+ case lists:member(CheckingApp, Info#app_info.incl_apps) of
+ true ->
+ case lists:member(Name, Visited) of
+ true ->
+ find_all(CheckingApp, Names, Apps, Visited, Found, NotFound);
+ false ->
+ find_all(CheckingApp, Names, Apps, Visited, Found, [Name | NotFound])
+ end;
+ false ->
+ find_all(CheckingApp, Names, Apps -- [App], Visited, [App|Found], NotFound)
+ end;
+ false ->
+ case lists:member(Name, Visited) of
+ true ->
+ find_all(CheckingApp, Names, Apps, Visited, Found, NotFound);
+ false ->
+ find_all(CheckingApp, Names, Apps, Visited, Found, [Name|NotFound])
+ end
+ end;
+find_all(_CheckingApp, [], Apps, _Visited, Found, NotFound) ->
+ {Found, Apps, NotFound}.
+
+del_apps([Name | Names], Apps) ->
+ del_apps(Names, lists:keydelete(Name, #app.name, Apps));
+del_apps([], Apps) ->
+ Apps.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Create the load path used in the generated script.
+%% If PathFlag is true a script intended to be used as a complete
+%% system (e.g. in an embbeded system), i.e. all applications are
+%% located under $ROOT/lib.
+%% Otherwise all paths are set according to dir per application.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Create the complete path.
+create_path(Apps, PathFlag, Variables) ->
+ make_set([cr_path(App, PathFlag, Variables) || App <- Apps]).
+
+%% Create the path to a specific application.
+%% (The otp_build flag is only used for OTP internal system make)
+cr_path(#app{label = Label}, true, []) ->
+ filename:join(["$ROOT", "lib", Label, "ebin"]);
+cr_path(#app{name = Name, vsn = Vsn, label = Label, active_dir = Dir}, true, Variables) ->
+ Tail = [Label, "ebin"],
+ case variable_dir(Dir, atom_to_list(Name), Vsn, Variables) of
+ {ok, VarDir} ->
+ filename:join([VarDir] ++ Tail);
+ _ ->
+ filename:join(["$ROOT", "lib"] ++ Tail)
+ end;
+cr_path(#app{name = Name}, otp_build, _) ->
+ filename:join(["$ROOT", "lib", atom_to_list(Name), "ebin"]);
+cr_path(#app{active_dir = Dir}, _, _) ->
+ filename:join([Dir, "ebin"]).
+
+variable_dir(Dir, Name, Vsn, [{Var,Path} | Variables]) ->
+ case lists:prefix(Path, Dir) of
+ true ->
+ D0 = strip_prefix(Path, Dir),
+ case strip_name_ebin(D0, Name, Vsn) of
+ {ok, D} ->
+ {ok, filename:join(["\$" ++ Var] ++ D)};
+ _ ->
+ %% We know at least that we are located
+ %% under the variable dir.
+ {ok, filename:join(["\$" ++ Var] ++ D0)}
+ end;
+ false ->
+ variable_dir(Dir, Name, Vsn, Variables)
+ end;
+variable_dir(_Dir, _, _, []) ->
+ false.
+
+strip_prefix(Path, Dir) ->
+ L = length(filename:split(Path)),
+ lists:nthtail(L, filename:split(Dir)).
+
+strip_name_ebin(Dir, Name, Vsn) ->
+ FullName = Name ++ "-" ++ Vsn,
+ case lists:reverse(Dir) of
+ ["ebin", Name | D] -> {ok, lists:reverse(D)};
+ ["ebin", FullName | D] -> {ok, lists:reverse(D)};
+ _ -> false
+ end.
+
+%% Create the path to the kernel and stdlib applications.
+create_mandatory_path(Apps, PathFlag, Variables) ->
+ Mandatory = [kernel, stdlib],
+ make_set(lists:map(fun(#app{name = Name} = App) ->
+ case lists:member(Name, Mandatory) of
+ true ->
+ cr_path(App, PathFlag, Variables);
+ false ->
+ ""
+ end
+ end,
+ Apps)).
+
+make_set([]) ->
+ [];
+make_set([""|T]) -> % Ignore empty items.
+ make_set(T);
+make_set([H|T]) ->
+ [H | [ Y || Y<- make_set(T),
+ Y =/= H]].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Generate rel, script and boot files
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+gen_rel_files(Sys, TargetDir) ->
+ try
+ Spec = spec_rel_files(Sys),
+ eval_spec(Spec, Sys#sys.root_dir, TargetDir)
+ catch
+ throw:{error, Text} ->
+ {error, Text}
+ end.
+
+spec_rel_files(#sys{rels = Rels} = Sys) ->
+ lists:append([do_spec_rel_files(R, Sys) || R <- Rels]).
+
+do_spec_rel_files(#rel{name = Name} = Rel, Sys) ->
+ RelFile = Name ++ ".rel",
+ ScriptFile = Name ++ ".script",
+ BootFile = Name ++ ".boot",
+ GenRel = gen_rel(Rel, Sys),
+ PathFlag = true,
+ Variables = [],
+ {ok, Script} = do_gen_script(Rel, Sys, PathFlag, Variables),
+ {ok, BootBin} = gen_boot(Script),
+ Date = date(),
+ Time = time(),
+ RelIoList = io_lib:format("%% rel generated at ~w ~w\n~p.\n\n",
+ [Date, Time, GenRel]),
+ ScriptIoList = io_lib:format("%% script generated at ~w ~w\n~p.\n\n",
+ [Date, Time, Script]),
+ [
+ {write_file, RelFile, RelIoList},
+ {write_file, ScriptFile, ScriptIoList},
+ {write_file, BootFile, BootBin}
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Generate a complete target system
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+gen_target(Sys, TargetDir) ->
+ try
+ Spec = do_gen_spec(Sys),
+ eval_spec(Spec, Sys#sys.root_dir, TargetDir)
+ catch
+ throw:{error, Text} ->
+ {error, Text}
+ end.
+
+gen_spec(Sys) ->
+ try
+ {ok, do_gen_spec(Sys)}
+ catch
+ throw:{error, Text} ->
+ {error, Text}
+ end.
+
+do_gen_spec(#sys{root_dir = RootDir,
+ incl_sys_filters = InclRegexps,
+ excl_sys_filters = ExclRegexps,
+ relocatable = Relocatable,
+ apps = Apps} = Sys) ->
+ {create_dir, _, SysFiles} = spec_dir(RootDir),
+ {ExclRegexps2, SysFiles2} = strip_sys_files(Relocatable, SysFiles, Apps, ExclRegexps),
+ RelFiles = spec_rel_files(Sys),
+ {InclRegexps2, BinFiles} = spec_bin_files(Sys, SysFiles, SysFiles2, RelFiles, InclRegexps),
+ LibFiles = spec_lib_files(Sys),
+ {BootVsn, StartFile} = spec_start_file(Sys),
+ SysFiles3 =
+ [
+ {create_dir, "releases",
+ [StartFile,
+ {create_dir,BootVsn, RelFiles}]},
+ {create_dir, "bin", BinFiles}
+ ] ++ SysFiles2,
+ %% io:format("InclRegexps2: ~p\n", [InclRegexps2]),
+ %% io:format("ExclRegexps2: ~p\n", [ExclRegexps2]),
+ SysFiles4 = filter_spec(SysFiles3, InclRegexps2, ExclRegexps2),
+ SysFiles5 = SysFiles4 ++ [{create_dir, "lib", LibFiles}],
+ check_sys(["bin", "erts", "lib"], SysFiles5),
+ SysFiles5.
+
+strip_sys_files(Relocatable, SysFiles, Apps, ExclRegexps) ->
+ ExclRegexps2 =
+ case Relocatable of
+ true ->
+ ExtraExcl = ["^erts.*/bin/.*src$"],
+ reltool_utils:decode_regexps(excl_sys_filters, {add, ExtraExcl}, ExclRegexps);
+ false ->
+ ExclRegexps
+ end,
+ {value, Erts} = lists:keysearch(erts, #app.name, Apps),
+ FilterErts =
+ fun(Spec) ->
+ File = element(2, Spec),
+ case lists:prefix("erts", File) of
+ true ->
+ if
+ File =:= Erts#app.label ->
+ replace_dyn_erl(Relocatable, Spec);
+ true ->
+ false
+ end;
+ false ->
+ true
+ end
+ end,
+ SysFiles2 = lists:zf(FilterErts, SysFiles),
+ SysFiles3 = lists:foldl(fun(F, Acc) -> lists:keydelete(F, 2, Acc) end,
+ SysFiles2,
+ ["releases", "lib", "bin"]),
+ {ExclRegexps2, SysFiles3}.
+
+replace_dyn_erl(false, _ErtsSpec) ->
+ true;
+replace_dyn_erl(true, {create_dir, ErtsDir, ErtsFiles}) ->
+ [{create_dir, _, BinFiles}] = safe_lookup_spec("bin", ErtsFiles),
+ case lookup_spec("dyn_erl", BinFiles) of
+ [] ->
+ case lookup_spec("erl.ini", BinFiles) of
+ [] ->
+ true;
+ [{copy_file, ErlIni}] ->
+ %% Remove Windows .ini file
+ BinFiles2 = lists:keydelete(ErlIni, 2, BinFiles),
+ ErtsFiles2 = lists:keyreplace("bin", 2, ErtsFiles, {create_dir, "bin", BinFiles2}),
+ {true, {create_dir, ErtsDir, ErtsFiles2}}
+ end;
+ [{copy_file, DynErlExe}] ->
+ %% Replace erl with dyn_erl
+ ErlExe = "erl" ++ filename:extension(DynErlExe),
+ BinFiles2 = lists:keydelete(DynErlExe, 2, BinFiles),
+ DynErlExe2 = filename:join([ErtsDir, "bin", DynErlExe]),
+ BinFiles3 = lists:keyreplace(ErlExe, 2, BinFiles2, {copy_file, ErlExe, DynErlExe2}),
+ ErtsFiles2 = lists:keyreplace("bin", 2, ErtsFiles, {create_dir, "bin", BinFiles3}),
+ {true, {create_dir, ErtsDir, ErtsFiles2}}
+ end.
+
+spec_bin_files(Sys, AllSysFiles, StrippedSysFiles, RelFiles, InclRegexps) ->
+ [{create_dir, ErtsLabel, ErtsFiles}] = safe_lookup_spec("erts", StrippedSysFiles),
+ [{create_dir, _, BinFiles}] = safe_lookup_spec("bin", ErtsFiles),
+ ErtsBin = filename:join([ErtsLabel, "bin"]),
+ Escripts = spec_escripts(Sys, ErtsBin, BinFiles),
+ Map = fun({copy_file, File}) ->
+ {copy_file, File, filename:join([ErtsBin, File])};
+ ({copy_file, NewFile, OldFile}) ->
+ {_, OldFile2} = abs_to_rel_path(ErtsBin, filename:join([ErtsBin, OldFile])),
+ {copy_file, NewFile, OldFile2}
+ end,
+
+ %% Do only copy those bin files from erts/bin that also exists in bin
+ [{create_dir, _, OldBinFiles}] = safe_lookup_spec("bin", AllSysFiles),
+ GoodNames = [F || {copy_file, F} <- OldBinFiles,
+ not lists:suffix(".boot", F),
+ not lists:suffix(".script", F)],
+ BinFiles2 = [Map(S) || S <- BinFiles, lists:member(element(2, S), GoodNames)],
+ BootFiles = [F || F <- RelFiles, lists:suffix(".boot", element(2, F))],
+ [{write_file, _, BootRel}] = safe_lookup_spec(Sys#sys.boot_rel ++ ".boot", BootFiles),
+ BootFiles2 = lists:keystore("start.boot", 2, BootFiles, {write_file, "start.boot", BootRel}),
+ MakeRegexp = fun(File) -> "^bin/" ++ element(2, File) ++ "(|.escript)$" end,
+ ExtraIncl = lists:map(MakeRegexp, Escripts),
+ InclRegexps2 = reltool_utils:decode_regexps(incl_sys_filters, {add, ExtraIncl}, InclRegexps),
+ {InclRegexps2, Escripts ++ BinFiles2 ++ BootFiles2}.
+
+spec_escripts(#sys{apps = Apps}, ErtsBin, BinFiles) ->
+ Filter = fun(#app{is_escript = IsEscript, is_included = IsIncl,
+ is_pre_included = IsPre, name = Name, active_dir = File}) ->
+ if
+ Name =:= ?MISSING_APP ->
+ false;
+ not IsEscript ->
+ false;
+ IsIncl; IsPre ->
+ {true, do_spec_escript(File, ErtsBin, BinFiles)};
+ true ->
+ false
+ end
+ end,
+ lists:flatten(lists:zf(Filter, Apps)).
+
+do_spec_escript(File, ErtsBin, BinFiles) ->
+ [{copy_file, EscriptExe}] = safe_lookup_spec("escript", BinFiles),
+ EscriptExt = ".escript",
+ Base = filename:basename(File, EscriptExt),
+ ExeExt = filename:extension(EscriptExe),
+ [{copy_file, Base ++ EscriptExt, File},
+ {copy_file, Base ++ ExeExt, filename:join([ErtsBin, EscriptExe])}].
+
+check_sys(Mandatory, SysFiles) ->
+ lists:foreach(fun(M) -> do_check_sys(M, SysFiles) end, Mandatory).
+
+do_check_sys(Prefix, Specs) ->
+ %%io:format("Prefix: ~p\n", [Prefix]),
+ case lookup_spec(Prefix, Specs) of
+ [] ->
+ reltool_utils:throw_error("Mandatory system directory ~s is not included",
+ [Prefix]);
+ _ ->
+ ok
+ end.
+
+spec_start_file(#sys{boot_rel = BootRelName,
+ rels = Rels,
+ apps = Apps}) ->
+ {value, Erts} = lists:keysearch(erts, #app.name, Apps),
+ {value, BootRel} = lists:keysearch(BootRelName, #rel.name, Rels),
+ Data = Erts#app.vsn ++ " " ++ BootRel#rel.vsn ++ "\n",
+ {BootRel#rel.vsn, {write_file, "start_erl.data", Data}}.
+
+lookup_spec(Prefix, Specs) ->
+ lists:filter(fun(S) -> lists:prefix(Prefix, element(2, S)) end, Specs).
+
+safe_lookup_spec(Prefix, Specs) ->
+ case lookup_spec(Prefix, Specs) of
+ [] ->
+ reltool_utils:throw_error("Mandatory system file ~s is not included", [Prefix]);
+ Match ->
+ Match
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Specify applications
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+spec_lib_files(#sys{apps = Apps} = Sys) ->
+ Filter = fun(#app{is_escript = IsEscript, is_included = IsIncl,
+ is_pre_included = IsPre, name = Name}) ->
+ if
+ Name =:= ?MISSING_APP ->
+ false;
+ IsEscript ->
+ false;
+ IsIncl; IsPre ->
+ true;
+ true ->
+ false
+ end
+ end,
+ SelectedApps = lists:filter(Filter, Apps),
+ check_apps([kernel, stdlib], SelectedApps),
+ lists:flatten([spec_app(App, Sys) || App <- SelectedApps]).
+
+check_apps([Mandatory | Names], Apps) ->
+ case lists:keymember(Mandatory, #app.name, Apps) of
+ false ->
+ reltool_utils:throw_error("Mandatory application ~p is not included in ~p",
+ [Mandatory, Apps]);
+ true ->
+ check_apps(Names, Apps)
+ end;
+check_apps([], _) ->
+ ok.
+
+spec_app(#app{name = Name,
+ mods = Mods,
+ active_dir = SourceDir,
+ incl_app_filters = AppInclRegexps,
+ excl_app_filters = AppExclRegexps} = App,
+ #sys{incl_app_filters = SysInclRegexps,
+ excl_app_filters = SysExclRegexps,
+ debug_info = SysDebugInfo} = Sys) ->
+ %% List files recursively
+ {create_dir, _, AppFiles} = spec_dir(SourceDir),
+
+ %% Replace ebin
+ AppUpFilename = atom_to_list(Name) ++ ".appup",
+ EbinDir = filename:join([SourceDir, "ebin"]),
+ OptAppUpFileSpec = spec_opt_copy_file(EbinDir, AppUpFilename),
+ OptAppFileSpec = spec_app_file(App, Sys, EbinDir),
+ ModSpecs = [spec_mod(M, SysDebugInfo) || M <- Mods, M#mod.is_included, M#mod.exists],
+ NewEbin = {create_dir, "ebin", OptAppUpFileSpec ++ OptAppFileSpec ++ ModSpecs},
+ AppFiles2 = lists:keystore("ebin", 2, AppFiles, NewEbin),
+
+ %% Apply file filter
+ InclRegexps = reltool_utils:default_val(AppInclRegexps, SysInclRegexps),
+ ExclRegexps = reltool_utils:default_val(AppExclRegexps, SysExclRegexps),
+ AppFiles3 = filter_spec(AppFiles2, InclRegexps, ExclRegexps),
+
+ %% Regular top directory and/or archive
+ spec_archive(App, Sys, AppFiles3).
+
+spec_archive(#app{label = Label,
+ active_dir = SourceDir,
+ incl_archive_filters = AppInclArchiveDirs,
+ excl_archive_filters = AppExclArchiveDirs,
+ archive_opts = AppArchiveOpts},
+ #sys{root_dir = RootDir,
+ incl_archive_filters = SysInclArchiveDirs,
+ excl_archive_filters = SysExclArchiveDirs,
+ archive_opts = SysArchiveOpts},
+ Files) ->
+ InclArchiveDirs = reltool_utils:default_val(AppInclArchiveDirs, SysInclArchiveDirs),
+ ExclArchiveDirs = reltool_utils:default_val(AppExclArchiveDirs, SysExclArchiveDirs),
+ ArchiveOpts = reltool_utils:default_val(AppArchiveOpts, SysArchiveOpts),
+ Match = fun(F) -> match(element(2, F), InclArchiveDirs, ExclArchiveDirs) end,
+ case lists:filter(Match, Files) of
+ [] ->
+ %% Nothing to archive
+ [spec_create_dir(RootDir, SourceDir, Label, Files)];
+ ArchiveFiles ->
+ OptDir =
+ case Files -- ArchiveFiles of
+ [] ->
+ [];
+ ExternalFiles ->
+ [spec_create_dir(RootDir, SourceDir, Label, ExternalFiles)]
+ end,
+ ArchiveOpts = reltool_utils:default_val(AppArchiveOpts, SysArchiveOpts),
+ ArchiveDir = spec_create_dir(RootDir, SourceDir, Label, ArchiveFiles),
+ [{archive, Label ++ ".ez", ArchiveOpts, [ArchiveDir]} | OptDir]
+ end.
+
+spec_dir(Dir) ->
+ Base = filename:basename(Dir),
+ case erl_prim_loader:read_file_info(Dir) of
+ {ok, #file_info{type = directory}} ->
+ case erl_prim_loader:list_dir(Dir) of
+ {ok, Files} ->
+ %% Directory
+ {create_dir, Base, [spec_dir(filename:join([Dir, F])) || F <- Files]};
+ error ->
+ reltool_utils:throw_error("list dir ~s failed\n", [Dir])
+ end;
+ {ok, #file_info{type = regular}} ->
+ %% Plain file
+ {copy_file, Base};
+ _ ->
+ reltool_utils:throw_error("read file info ~s failed\n", [Dir])
+ end.
+
+spec_mod(Mod, DebugInfo) ->
+ File = atom_to_list(Mod#mod.name) ++ code:objfile_extension(),
+ case reltool_utils:default_val(Mod#mod.debug_info, DebugInfo) of
+ keep ->
+ {copy_file, File};
+ strip ->
+ {strip_beam, File}
+ end.
+
+spec_app_file(#app{name = Name,
+ info = Info,
+ mods = Mods,
+ app_file = AppFile} = App,
+ #sys{app_file = SysAppFile},
+ EbinDir) ->
+ AppFilename = atom_to_list(Name) ++ ".app",
+ case reltool_utils:default_val(AppFile, SysAppFile) of
+ keep ->
+ %% Copy if it exists
+ spec_opt_copy_file(EbinDir, AppFilename);
+ strip ->
+ %% Remove non-included modules
+ %% Generate new file
+ ModNames = [M#mod.name || M <- Mods,
+ M#mod.is_included,
+ lists:member(M#mod.name,
+ Info#app_info.modules)],
+ App2 = App#app{info = Info#app_info{modules = ModNames}},
+ Contents = gen_app(App2),
+ AppIoList = io_lib:format("%% app generated at ~w ~w\n~p.\n\n",
+ [date(), time(), Contents]),
+ [{write_file, AppFilename, AppIoList}];
+ all ->
+ %% Include all included modules
+ %% Generate new file
+ ModNames = [M#mod.name || M <- Mods, M#mod.is_included],
+ App2 = App#app{info = Info#app_info{modules = ModNames}},
+ Contents = gen_app(App2),
+ AppIoList = io_lib:format("%% app generated at ~w ~w\n~p.\n\n",
+ [date(), time(), Contents]),
+ [{write_file, AppFilename, AppIoList}]
+
+ end.
+
+spec_opt_copy_file(DirName, BaseName) ->
+ case filelib:is_regular(filename:join([DirName, BaseName]), erl_prim_loader) of
+ true -> [{copy_file, BaseName}];
+ false -> []
+ end.
+
+spec_create_dir(RootDir, SourceDir, BaseDir, Files) ->
+ LibDir = filename:join([RootDir, "lib"]),
+ case abs_to_rel_path(LibDir, SourceDir) of
+ {relative, Dir} -> {create_dir, Dir, Files};
+ {absolute, Dir} -> {create_dir, BaseDir, Dir, Files}
+ end.
+
+abs_to_rel_path(RootDir, SourcePath) ->
+ R = filename:split(RootDir),
+ S = filename:split(SourcePath),
+ abs_to_rel_path(R, S, SourcePath).
+
+abs_to_rel_path([H | R], [H | S], SourcePath) ->
+ abs_to_rel_path(R, S, SourcePath);
+abs_to_rel_path([], S, _SourcePath) ->
+ {relative, filename:join(S)};
+abs_to_rel_path(_, _, SourcePath) ->
+ {absolute, SourcePath}.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Evaluate specification
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+eval_spec(Spec, SourceDir, TargetDir) ->
+ SourceDir2 = filename:absname(SourceDir),
+ TargetDir2 = filename:absname(TargetDir),
+ try
+ case filelib:is_dir(TargetDir2) of
+ true ->
+ do_eval_spec(Spec, SourceDir2, SourceDir2, TargetDir2),
+ ok;
+ false ->
+ {error, TargetDir2 ++ ": " ++ file:format_error(enoent)}
+ end
+ catch
+ throw:{error, Text} ->
+ cleanup_spec(Spec, TargetDir2),
+ {error, Text}
+ end.
+
+do_eval_spec(List, OrigSourceDir, SourceDir, TargetDir) when is_list(List) ->
+ lists:foreach(fun(F) -> do_eval_spec(F, OrigSourceDir, SourceDir, TargetDir) end, List);
+%% do_eval_spec({source_dir, SourceDir2, Spec}, OrigSourceDir, _SourceDir, TargetDir) ->
+%% %% Source dir is absolute or relative the original source dir
+%% SourceDir3 = filename:join([OrigSourceDir, SourceDir2]),
+%% do_eval_spec(Spec, OrigSourceDir, SourceDir3, TargetDir);
+do_eval_spec({create_dir, Dir, Files}, OrigSourceDir, SourceDir, TargetDir) ->
+ SourceDir2 = filename:join([SourceDir, Dir]),
+ TargetDir2 = filename:join([TargetDir, Dir]),
+ reltool_utils:create_dir(TargetDir2),
+ do_eval_spec(Files, OrigSourceDir, SourceDir2, TargetDir2);
+do_eval_spec({create_dir, NewDir, OldDir, Files}, OrigSourceDir, _SourceDir, TargetDir) ->
+ SourceDir2 = filename:join([OrigSourceDir, OldDir]),
+ TargetDir2 = filename:join([TargetDir, NewDir]),
+ reltool_utils:create_dir(TargetDir2),
+ do_eval_spec(Files, SourceDir2, SourceDir2, TargetDir2);
+do_eval_spec({archive, Archive, Options, Files}, OrigSourceDir, SourceDir, TargetDir) ->
+ TmpSpec = {create_dir, "tmp", Files},
+ TmpDir = filename:join([TargetDir, "tmp"]),
+ reltool_utils:create_dir(TmpDir),
+ do_eval_spec(Files, OrigSourceDir, SourceDir, TmpDir),
+
+ ArchiveFile = filename:join([TargetDir, Archive]),
+ Files2 = [element(2, F) || F <- Files],
+ Res = zip:create(ArchiveFile, Files2, [{cwd, TmpDir} | Options]),
+
+ cleanup_spec(TmpSpec, TargetDir),
+ case Res of
+ {ok, _} ->
+ ok;
+ {error, Reason} ->
+ reltool_utils:throw_error("create archive ~s: ~p\n", [ArchiveFile, Reason])
+ end;
+do_eval_spec({copy_file, File}, _OrigSourceDir, SourceDir, TargetDir) ->
+ SourceFile = filename:join([SourceDir, File]),
+ TargetFile = filename:join([TargetDir, File]),
+ reltool_utils:copy_file(SourceFile, TargetFile);
+do_eval_spec({copy_file, NewFile, OldFile}, OrigSourceDir, _SourceDir, TargetDir) ->
+ SourceFile = filename:join([OrigSourceDir, OldFile]),
+ TargetFile = filename:join([TargetDir, NewFile]),
+ reltool_utils:copy_file(SourceFile, TargetFile);
+do_eval_spec({write_file, File, IoList}, _OrigSourceDir, _SourceDir, TargetDir) ->
+ TargetFile = filename:join([TargetDir, File]),
+ reltool_utils:write_file(TargetFile, IoList);
+do_eval_spec({strip_beam, File}, _OrigSourceDir, SourceDir, TargetDir) ->
+ SourceFile = filename:join([SourceDir, File]),
+ TargetFile = filename:join([TargetDir, File]),
+ BeamBin = reltool_utils:read_file(SourceFile),
+ {ok, {_, BeamBin2}} = beam_lib:strip(BeamBin),
+ reltool_utils:write_file(TargetFile, BeamBin2).
+
+cleanup_spec(List, TargetDir) when is_list(List) ->
+ lists:foreach(fun(F)-> cleanup_spec(F, TargetDir) end, List);
+%% cleanup_spec({source_dir, _SourceDir, Spec}, TargetDir) ->
+%% cleanup_spec(Spec, TargetDir);
+cleanup_spec({create_dir, Dir, Files}, TargetDir) ->
+ TargetDir2 = filename:join([TargetDir, Dir]),
+ cleanup_spec(Files, TargetDir2),
+ file:del_dir(TargetDir2);
+cleanup_spec({create_dir, NewDir, _OldDir, Files}, TargetDir) ->
+ TargetDir2 = filename:join([TargetDir, NewDir]),
+ cleanup_spec(Files, TargetDir2),
+ file:del_dir(TargetDir2);
+cleanup_spec({archive, Archive, _Options, Files}, TargetDir) ->
+ TargetFile = filename:join([TargetDir, Archive]),
+ file:delete(TargetFile),
+ TmpDir = filename:join([TargetDir, "tmp"]),
+ cleanup_spec(Files, TmpDir),
+ file:del_dir(TmpDir);
+cleanup_spec({copy_file, File}, TargetDir) ->
+ TargetFile = filename:join([TargetDir, File]),
+ file:delete(TargetFile);
+cleanup_spec({copy_file, NewFile, _OldFile}, TargetDir) ->
+ TargetFile = filename:join([TargetDir, NewFile]),
+ file:delete(TargetFile);
+cleanup_spec({write_file, File, _IoList}, TargetDir) ->
+ TargetFile = filename:join([TargetDir, File]),
+ file:delete(TargetFile);
+cleanup_spec({strip_beam, File}, TargetDir) ->
+ TargetFile = filename:join([TargetDir, File]),
+ file:delete(TargetFile).
+
+filter_spec(List, InclRegexps, ExclRegexps) ->
+ do_filter_spec("", List, InclRegexps, ExclRegexps).
+
+do_filter_spec(Path, List, InclRegexps, ExclRegexps) when is_list(List) ->
+ lists:zf(fun(File) -> do_filter_spec(Path, File, InclRegexps, ExclRegexps) end, List);
+%% do_filter_spec(Path, {source_dir, _SourceDir, Spec}, InclRegexps, ExclRegexps) ->
+%% do_filter_spec(Path, Spec, InclRegexps, ExclRegexps);
+do_filter_spec(Path, {create_dir, Dir, Files}, InclRegexps, ExclRegexps) ->
+ Path2 = opt_join(Path, Dir),
+ case do_filter_spec(Path2, Files, InclRegexps, ExclRegexps) of
+ [] ->
+ case match(Path2, InclRegexps, ExclRegexps) of
+ true ->
+ {true, {create_dir, Dir, []}};
+ false ->
+ false
+ end;
+ Files2 when is_list(Files2) ->
+ {true, {create_dir, Dir, Files2}}
+ end;
+do_filter_spec(Path, {create_dir, NewDir, OldDir, Files}, InclRegexps, ExclRegexps) ->
+ Path2 = opt_join(Path, NewDir),
+ case do_filter_spec(Path2, Files, InclRegexps, ExclRegexps) of
+ [] ->
+ case match(Path2, InclRegexps, ExclRegexps) of
+ true ->
+ {true, {create_dir, NewDir, OldDir, []}};
+ false ->
+ false
+ end;
+ Files2 when is_list(Files2) ->
+ {true, {create_dir, NewDir, OldDir, Files2}}
+ end;
+do_filter_spec(Path, {archive, Archive, Options, Files}, InclRegexps, ExclRegexps) ->
+ case do_filter_spec(Path, Files, InclRegexps, ExclRegexps) of
+ [] ->
+ case match(Path, InclRegexps, ExclRegexps) of
+ true ->
+ {true, {archive, Archive, Options, []}};
+ false ->
+ false
+ end;
+ Files2 when is_list(Files2) ->
+ {true, {archive, Archive, Options, Files2}}
+ end;
+do_filter_spec(Path, {copy_file, File}, InclRegexps, ExclRegexps) ->
+ Path2 = opt_join(Path, File),
+ match(Path2, InclRegexps, ExclRegexps);
+do_filter_spec(Path, {copy_file, NewFile, _OldFile}, InclRegexps, ExclRegexps) ->
+ Path2 = opt_join(Path, NewFile),
+ match(Path2, InclRegexps, ExclRegexps);
+do_filter_spec(Path, {write_file, File, _IoList}, InclRegexps, ExclRegexps) ->
+ Path2 = opt_join(Path, File),
+ match(Path2, InclRegexps, ExclRegexps);
+do_filter_spec(Path, {strip_beam, File}, InclRegexps, ExclRegexps) ->
+ Path2 = opt_join(Path, File),
+ match(Path2, InclRegexps, ExclRegexps).
+
+opt_join([], File) ->
+ File;
+opt_join(Path, File) ->
+ filename:join([Path, File]).
+
+match(String, InclRegexps, ExclRegexps) ->
+ %%case
+ match(String, InclRegexps) andalso not match(String, ExclRegexps).
+%% of
+%% true ->
+%% true;
+%% false ->
+%% io:format("no match: ~p\n"
+%% " incl: ~p\n"
+%% " excl: ~p\n",
+%% [String, InclRegexps, ExclRegexps]),
+%% false
+%% end.
+
+%% Match at least one regexp
+match(_String, []) ->
+ false;
+match(String, [#regexp{source = _, compiled = MP} | Regexps]) ->
+ %% io:format("Regexp: ~p ~p\n", [String, Regexp]),
+ case re:run(String, MP, [{capture, none}]) of
+ nomatch -> match(String, Regexps);
+ match -> true
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+install(RelName, TargetDir) ->
+ try
+ do_install(RelName, TargetDir)
+ catch
+ throw:{error, Text} ->
+ {error, Text}
+ end.
+
+do_install(RelName, TargetDir) ->
+ TargetDir2 = filename:absname(TargetDir),
+ RelDir = filename:join([TargetDir2, "releases"]),
+ DataFile = filename:join([RelDir, "start_erl.data"]),
+ Bin = reltool_utils:read_file(DataFile),
+ case string:tokens(binary_to_list(Bin), " \n") of
+ [ErlVsn, RelVsn | _] ->
+ ErtsBinDir = filename:join([TargetDir2, "erts-" ++ ErlVsn, "bin"]),
+ BinDir = filename:join([TargetDir2, "bin"]),
+ case os:type() of
+ {win32, _} ->
+ NativeRootDir = filename:nativename(TargetDir2),
+ %% NativeBinDir = filename:nativename(filename:join([BinDir, "win32"])),
+ NativeBinDir = filename:nativename(BinDir),
+ IniData = ["[erlang]\r\n",
+ "Bindir=", NativeBinDir, "\r\n",
+ "Progname=erl\r\n",
+ "Rootdir=", NativeRootDir, "\r\n"],
+ IniFile = filename:join([BinDir, "erl.ini"]),
+ ok = file:write_file(IniFile, IniData);
+ _ ->
+ subst_src_scripts(start_scripts(), ErtsBinDir, BinDir,
+ [{"FINAL_ROOTDIR", TargetDir2}, {"EMU", "beam"}],
+ [preserve])
+ end,
+ RelFile = filename:join([RelDir, RelVsn, RelName ++ ".rel"]),
+ ok = release_handler:create_RELEASES(TargetDir2, RelFile),
+ ok;
+ _ ->
+ reltool_utils:throw_error("~s: Illegal syntax.\n", [DataFile])
+ end.
+
+subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
+ Fun = fun(Script) -> subst_src_script(Script, SrcDir, DestDir, Vars, Opts) end,
+ lists:foreach(Fun, Scripts).
+
+subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
+ subst_file(filename:join([SrcDir, Script ++ ".src"]),
+ filename:join([DestDir, Script]),
+ Vars,
+ Opts).
+
+subst_file(Src, Dest, Vars, Opts) ->
+ Bin = reltool_utils:read_file(Src),
+ Chars = subst(binary_to_list(Bin), Vars),
+ reltool_utils:write_file(Dest, Chars),
+ case lists:member(preserve, Opts) of
+ true ->
+ FileInfo = reltool_utils:read_file_info(Src),
+ reltool_utils:write_file_info(Dest, FileInfo);
+ false ->
+ ok
+ end.
+
+%% subst(Str, Vars)
+%% Vars = [{Var, Val}]
+%% Var = Val = string()
+%% Substitute all occurrences of %Var% for Val in Str, using the list
+%% of variables in Vars.
+%%
+subst(Str, Vars) ->
+ subst(Str, Vars, []).
+
+subst([$%, C | Rest], Vars, Result) when $A =< C, C =< $Z ->
+ subst_var([C| Rest], Vars, Result, []);
+subst([$%, C | Rest], Vars, Result) when $a =< C, C =< $z ->
+ subst_var([C| Rest], Vars, Result, []);
+subst([$%, C | Rest], Vars, Result) when C == $_ ->
+ subst_var([C| Rest], Vars, Result, []);
+subst([C| Rest], Vars, Result) ->
+ subst(Rest, Vars, [C| Result]);
+subst([], _Vars, Result) ->
+ lists:reverse(Result).
+
+subst_var([$%| Rest], Vars, Result, VarAcc) ->
+ Key = lists:reverse(VarAcc),
+ case lists:keysearch(Key, 1, Vars) of
+ {value, {Key, Value}} ->
+ subst(Rest, Vars, lists:reverse(Value, Result));
+ false ->
+ subst(Rest, Vars, [$% | VarAcc ++ [$% | Result]])
+ end;
+subst_var([C| Rest], Vars, Result, VarAcc) ->
+ subst_var(Rest, Vars, Result, [C| VarAcc]);
+subst_var([], Vars, Result, VarAcc) ->
+ subst([], Vars, [VarAcc ++ [$% | Result]]).
+
+
+start_scripts() ->
+ ["erl", "start", "start_erl"].
diff --git a/lib/reltool/src/reltool_utils.erl b/lib/reltool/src/reltool_utils.erl
new file mode 100644
index 0000000000..8d52ade9be
--- /dev/null
+++ b/lib/reltool/src/reltool_utils.erl
@@ -0,0 +1,555 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+
+-module(reltool_utils).
+
+%% Public
+-compile([export_all]).
+
+-include_lib("kernel/include/file.hrl").
+-include_lib("wx/include/wx.hrl").
+-include("reltool.hrl").
+
+root_dir() ->
+ code:root_dir().
+
+erl_libs() ->
+ case os:getenv("ERL_LIBS") of
+ false ->
+ [];
+ LibStr ->
+ string:tokens(LibStr, ":;")
+ end.
+
+lib_dirs(Dir) ->
+ case erl_prim_loader:list_dir(Dir) of
+ {ok, Files} ->
+ [F || F <- Files,
+ filelib:is_dir(filename:join([Dir, F]),
+ erl_prim_loader)];
+ error ->
+ []
+ end.
+
+%% "asn1-1.6.2" -> {"asn1", "1.6.2"}; "asn1" -> {"asn1", ""}
+split_app_name(Name) ->
+ Pred =
+ fun(Elem) ->
+ if
+ Elem =:= $\. -> true;
+ Elem >= $0, Elem =< $9 -> true;
+ true -> false
+ end
+ end,
+ case lists:splitwith(Pred, lists:reverse(Name)) of
+ {Vsn, [$- | App]} ->
+ {list_to_atom(lists:reverse(App)), lists:reverse(Vsn)};
+ _ ->
+ {list_to_atom(Name), ""}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+prim_consult(Bin) when is_binary(Bin) ->
+ case erl_scan:string(binary_to_list(Bin)) of
+ {ok, Tokens, _EndLine} ->
+ prim_parse(Tokens, []);
+ {error, {_ErrorLine, Module, Reason}, _EndLine} ->
+ {error, Module:format_error(Reason)}
+ end;
+prim_consult(FullName) when is_list(FullName) ->
+ case erl_prim_loader:get_file(FullName) of
+ {ok, Bin, _} ->
+ prim_consult(Bin);
+ error ->
+ {error, file:format_error(enoent)}
+ end.
+
+prim_parse(Tokens, Acc) ->
+ case lists:splitwith(fun(T) -> element(1,T) =/= dot end, Tokens) of
+ {[], []} ->
+ {ok, lists:reverse(Acc)};
+ {Tokens2, [{dot,_} = Dot | Rest]} ->
+ case erl_parse:parse_term(Tokens2 ++ [Dot]) of
+ {ok, Term} ->
+ prim_parse(Rest, [Term | Acc]);
+ {error, {_ErrorLine, Module, Reason}} ->
+ {error, Module:format_error(Reason)}
+ end;
+ {Tokens2, []} ->
+ case erl_parse:parse_term(Tokens2) of
+ {ok, Term} ->
+ {ok, lists:reverse([Term | Acc])};
+ {error, {_ErrorLine, Module, Reason}} ->
+ {error, Module:format_error(Reason)}
+ end
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+default_rels() ->
+ Kernel = #rel_app{name = kernel, incl_apps = []},
+ Stdlib = #rel_app{name = stdlib, incl_apps = []},
+ Sasl = #rel_app{name = sasl, incl_apps = []},
+ [
+ #rel{name = ?DEFAULT_REL_NAME,
+ vsn = "1.0",
+ rel_apps = [Kernel, Stdlib]},
+ #rel{name = "start_sasl",
+ vsn = "1.0",
+ rel_apps = [Kernel, Sasl, Stdlib]}
+ ].
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+assign_image_list(ListCtrl) ->
+ Art = wxImageList:new(16,16),
+ [wxImageList:add(Art, wxArtProvider:getBitmap(Image, [{size, {16,16}}]))
+ || Image <- ["wxART_ERROR",
+ "wxART_WARNING",
+ "wxART_QUESTION",
+ "wxART_TICK_MARK",
+ "wxART_CROSS_MARK",
+ "wxART_GO_HOME"]],
+ wxListCtrl:assignImageList(ListCtrl, Art, ?wxIMAGE_LIST_SMALL).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_latest_resize(#wx{obj = ObjRef, event = #wxSize{}} = Wx) ->
+ receive
+ #wx{obj = ObjRef, event = #wxSize{}} = Wx2 ->
+ get_latest_resize(Wx2)
+ after 10 ->
+ Wx
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+mod_conds() ->
+ ["all (ebin + app file)", "ebin + derived", "app file + derived", "derived", "none"].
+
+list_to_mod_cond(List) ->
+ case List of
+ "all" ++ _ -> all;
+ "ebin" ++ _ -> ebin;
+ "app" ++ _ -> app;
+ "derived" -> derived;
+ "none" -> none
+ end.
+
+mod_cond_to_index(ModCond) ->
+ case ModCond of
+ all -> 0;
+ ebin -> 1;
+ app -> 2;
+ derived -> 3;
+ undefined -> 3;
+ none -> 4
+ end.
+
+incl_conds() ->
+ ["include", "exclude", "derived"].
+
+list_to_incl_cond(List) ->
+ case List of
+ "include" -> include;
+ "exclude" -> exclude;
+ "derived" -> derived
+ end.
+
+incl_cond_to_index(ModCond) ->
+ case ModCond of
+ include -> 0;
+ exclude -> 1;
+ derived -> 2
+ end.
+
+elem_to_index(Elem, List) ->
+ elem_to_index(Elem, List, 1).
+
+elem_to_index(Elem, [H | T], Index) ->
+ case Elem =:= H of
+ true -> Index;
+ false -> elem_to_index(Elem, T, Index + 1)
+ end;
+elem_to_index(Elem, [], _) ->
+ erlang:error({not_found, Elem}).
+
+app_dir_test(Dir1, Dir2) ->
+ {Name1, Vsn1, Parent1} = split_app_dir(Dir1),
+ {Name2, Vsn2, Parent2} = split_app_dir(Dir2),
+ if
+ Name1 < Name2 -> true;
+ Name1 > Name2 -> false;
+ Vsn1 < Vsn2 -> false;
+ Vsn1 > Vsn2 -> true;
+ Parent1 < Parent2 -> true;
+ true -> false
+ end.
+
+split_app_dir(Dir) ->
+ ParentDir = filename:dirname(Dir),
+ Base = filename:basename(Dir),
+ {Name, Vsn} = split_app_name(Base),
+ Vsn2 =
+ try
+ [list_to_integer(N) || N <- string:tokens(Vsn, ".")]
+ catch
+ _:_ ->
+ Vsn
+ end,
+ {Name, Vsn2, ParentDir}.
+
+get_item(ListCtrl) ->
+ case wxListCtrl:getItemCount(ListCtrl) of
+ 0 ->
+ undefined;
+ _ ->
+ case wxListCtrl:getNextItem(ListCtrl,
+ -1,
+ [{geometry, ?wxLIST_NEXT_ALL},
+ {state, ?wxLIST_STATE_SELECTED}]) of
+ -1 ->
+ ItemNo = wxListCtrl:getTopItem(ListCtrl),
+ case wxListCtrl:getItemText(ListCtrl, ItemNo) of
+ "" ->
+ undefined;
+ Text ->
+ {ItemNo, Text}
+ end;
+ ItemNo ->
+ Text = wxListCtrl:getItemText(ListCtrl, ItemNo),
+ {ItemNo, Text}
+ end
+ end.
+
+get_items(ListCtrl) ->
+ case wxListCtrl:getItemCount(ListCtrl) of
+ 0 ->
+ [];
+ Count ->
+ case get_selected_items(ListCtrl, -1, []) of
+ [] ->
+ ItemNo = wxListCtrl:getTopItem(ListCtrl),
+ case wxListCtrl:getItemText(ListCtrl, ItemNo) of
+ "" ->
+ [];
+ Text when Text =/= ?MISSING_APP_TEXT ->
+ [{ItemNo, Text}];
+ _MissingText when Count > 1 ->
+ case wxListCtrl:getItemText(ListCtrl, ItemNo + 1) of
+ "" ->
+ [];
+ Text ->
+ [{ItemNo, Text}]
+ end;
+ _MissingText ->
+ []
+ end;
+ Items ->
+ Items
+ end
+ end.
+
+get_selected_items(ListCtrl, PrevItem, Acc) ->
+ case wxListCtrl:getNextItem(ListCtrl,
+ PrevItem,
+ [{geometry, ?wxLIST_NEXT_ALL},
+ {state, ?wxLIST_STATE_SELECTED}]) of
+ -1 ->
+ Acc;
+ ItemNo ->
+ case wxListCtrl:getItemText(ListCtrl, ItemNo) of
+ Text when Text =/= ?MISSING_APP_TEXT ->
+ get_selected_items(ListCtrl, ItemNo, [{ItemNo, Text} | Acc]);
+ _Text ->
+ get_selected_items(ListCtrl, ItemNo, Acc)
+ end
+ end.
+
+select_items(_ListCtrl, _OldItems, []) ->
+ %% No new items. Nothing to select.
+ false;
+select_items(ListCtrl, [], Items) ->
+ %% No old selection. Select first.
+ select_item(ListCtrl, Items);
+select_items(ListCtrl, _OldItems, [Item]) ->
+ %% Only one new item. Select it.
+ select_item(ListCtrl, [Item]);
+select_items(ListCtrl, OldItems, NewItems) ->
+ %% Try to propagate old selection to new items.
+ Filter =
+ fun({_OldItemNo, Text}) ->
+ case lists:keysearch(Text, 2, NewItems) of
+ {value, Item} -> {true, Item};
+ false -> false
+ end
+ end,
+ case lists:zf(Filter, OldItems) of
+ [] ->
+ %% None of the old selections are valid. Select the first.
+ select_item(ListCtrl, NewItems);
+ ValidItems ->
+ %% Some old selections are still valid. Select them again.
+ lists:foreach(fun(Item) -> select_item(ListCtrl, [Item]) end, ValidItems)
+ end.
+
+select_item(ListCtrl, [{ItemNo, Text} | Items]) ->
+ case Text =:= ?MISSING_APP_TEXT of
+ true ->
+ select_item(ListCtrl, Items);
+ false ->
+ StateMask = ?wxLIST_STATE_SELECTED,
+ State = wxListCtrl:getItemState(ListCtrl, ItemNo, StateMask),
+ State2 = State bor ?wxLIST_STATE_SELECTED,
+ wxListCtrl:setItemState(ListCtrl, ItemNo, State2, StateMask),
+ wxListCtrl:refreshItem(ListCtrl, ItemNo)
+ end;
+select_item(_ListCtrl, []) ->
+ ok.
+
+safe_keysearch(Key, Pos, List, Mod, Line) ->
+ case lists:keysearch(Key, Pos, List) of
+ false ->
+ io:format("~p(~p): lists:keysearch(~p, ~p, ~p) -> false\n",
+ [Mod, Line, Key, Pos, List]),
+ erlang:error({Mod, Line, lists, keysearch, [Key, Pos, List]});
+ {value, Val} ->
+ Val
+ end.
+
+print(X, X, Format, Args) ->
+ io:format(Format, Args);
+print(_, _, _, _) ->
+ ok.
+
+%% -define(SAFE(M,F,A), safe(M, F, A, ?MODULE, ?LINE)).
+%%
+%% safe(M, F, A, Mod, Line) ->
+%% case catch apply(M, F, A) of
+%% {'EXIT', Reason} ->
+%% io:format("~p(~p): ~p:~p~p -> ~p\n", [Mod, Line, M, F, A, Reason]),
+%% timer:sleep(infinity);
+%% Res ->
+%% Res
+%% end.
+
+return_first_error(Status, NewError) when is_list(NewError) ->
+ case Status of
+ {ok, _Warnings} ->
+ {error, NewError};
+ {error, OldError} ->
+ {error, OldError}
+ end.
+
+add_warning(Status, Warning) ->
+ case Status of
+ {ok, Warnings} ->
+ {ok, [Warning | Warnings]};
+ {error, Error} ->
+ {error, Error}
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+create_dir(Dir) ->
+ filelib:ensure_dir(Dir),
+ case file:make_dir(Dir) of
+ ok ->
+ ok;
+ {error, eexist} ->
+ ok;
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ throw_error("create dir ~s: ~s\n", [Dir, Text])
+ end.
+
+list_dir(Dir) ->
+ case erl_prim_loader:list_dir(Dir) of
+ {ok, Files} ->
+ Files;
+ error ->
+ Text = file:format_error(enoent),
+ throw_error("list dir ~s: ~s\n", [Dir, Text])
+ end.
+
+read_file_info(File) ->
+ case file:read_file_info(File) of
+ {ok, Info} ->
+ Info;
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ throw_error("read file info ~s: ~s\n", [File, Text])
+ end.
+
+write_file_info(File, Info) ->
+ case file:write_file_info(File, Info) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ throw_error("write file info ~s: ~s\n", [File, Text])
+ end.
+
+read_file(File) ->
+ case file:read_file(File) of
+ {ok, Bin} ->
+ Bin;
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ throw_error("read file ~s: ~s\n", [File, Text])
+ end.
+
+write_file(File, IoList) ->
+ case file:write_file(File, IoList) of
+ ok ->
+ ok;
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ throw_error("write file ~s: ~s\n", [File, Text])
+ end.
+
+recursive_delete(Dir) ->
+ case filelib:is_dir(Dir) of
+ true ->
+ case file:list_dir(Dir) of
+ {ok, Files} ->
+ Fun = fun(F) -> recursive_delete(filename:join([Dir, F])) end,
+ lists:foreach(Fun, Files),
+ delete(Dir, directory);
+ {error, enoent} ->
+ ok;
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ throw_error("delete file ~s: ~s\n", [Dir, Text])
+ end;
+ false ->
+ delete(Dir, regular)
+ end.
+
+delete(File, Type) ->
+ case do_delete(File, Type) of
+ ok ->
+ ok;
+ {error, enoent} ->
+ ok;
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ throw_error("delete file ~s: ~s\n", [File, Text])
+ end.
+
+do_delete(File, regular) ->
+ file:delete(File);
+do_delete(Dir, directory) ->
+ file:del_dir(Dir).
+
+recursive_copy_file(From, To) ->
+ case erl_prim_loader:list_dir(From) of
+ {ok, Files} ->
+ %% Copy all files in the directory
+ create_dir(To),
+ Copy =
+ fun(F) ->
+ recursive_copy_file(filename:join([From, F]),
+ filename:join([To, F]))
+ end,
+ lists:foreach(Copy, Files);
+ error ->
+ %% Copy single file
+ copy_file(From, To)
+ end.
+
+copy_file(From, To) ->
+ case erl_prim_loader:get_file(From) of
+ {ok, Bin, _} ->
+ case file:write_file(To, Bin) of
+ ok ->
+ FromInfo = read_file_info(From),
+ ToInfo = read_file_info(To),
+ FromMode = FromInfo#file_info.mode,
+ ToMode = ToInfo#file_info.mode,
+ ToMode2 = FromMode bor ToMode,
+ FileInfo = FromInfo#file_info{mode = ToMode2},
+ write_file_info(To, FileInfo),
+ ok;
+ {error, Reason} ->
+ Text = file:format_error(Reason),
+ throw_error("copy file ~s -> ~s: ~s\n", [From, To, Text])
+ end;
+ error ->
+ Text = file:format_error(enoent),
+ throw_error("copy file ~s -> ~s: ~s\n", [From, To, Text])
+ end.
+
+throw_error(Format, Args) ->
+ throw({error, lists:flatten(io_lib:format(Format, Args))}).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+decode_regexps(Key, {add, Regexps}, Old) when is_list(Regexps) ->
+ do_decode_regexps(Key, Regexps, Old);
+decode_regexps(_Key, {del, Regexps}, Old) when is_list(Regexps) ->
+ [Re || Re <- Old, not lists:member(Re#regexp.source, Regexps)];
+decode_regexps(Key, Regexps, _Old) when is_list(Regexps) ->
+ do_decode_regexps(Key, Regexps, []);
+decode_regexps(Key, Regexps, _Old) when is_list(Regexps) ->
+ Text = lists:flatten(io_lib:format("~p", [{Key, Regexps}])),
+ throw({error, "Illegal option: " ++ Text}).
+
+do_decode_regexps(Key, [Regexp | Regexps], Acc) ->
+ case catch re:compile(Regexp, []) of
+ {ok, MP} ->
+ do_decode_regexps(Key, Regexps, [#regexp{source = Regexp, compiled = MP} | Acc]);
+ _ ->
+ Text = lists:flatten(io_lib:format("~p", [{Key, Regexp}])),
+ throw({error, "Illegal option: " ++ Text})
+ end;
+do_decode_regexps(_Key, [], Acc) ->
+ lists:sort(Acc).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+default_val(Val, Default) ->
+ case Val of
+ undefined -> Default;
+ _ -> Val
+ end.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+call(Name, Msg) when is_atom(Name) ->
+ call(whereis(Name), Msg);
+call(Pid, Msg) when is_pid(Pid) ->
+ Ref = erlang:monitor(process, Pid),
+ Pid ! {call, self(), Ref, Msg},
+ receive
+ {Ref, Reply} ->
+ Reply;
+ {'EXIT', Pid, Reason} ->
+ erlang:demonitor(Ref, [flush]),
+ {error, Reason};
+ {'DOWN', Ref, _, _, Reason} ->
+ {error, Reason}
+ end.
+
+cast(Pid, Msg) ->
+ Pid ! {cast, self(), Msg},
+ ok.
+
+reply(Pid, Ref, Msg) ->
+ Pid ! {Ref, Msg}.
diff --git a/lib/reltool/vsn.mk b/lib/reltool/vsn.mk
new file mode 100644
index 0000000000..d5ca40619c
--- /dev/null
+++ b/lib/reltool/vsn.mk
@@ -0,0 +1,26 @@
+# This is an -*-makefile-*- file.
+# %CopyrightBegin%
+#
+# Copyright Ericsson AB 2009. All Rights Reserved.
+#
+# The contents of this file are subject to the Erlang Public License,
+# Version 1.1, (the "License"); you may not use this file except in
+# compliance with the License. You should have received a copy of the
+# Erlang Public License along with this software. If not, it can be
+# retrieved online at http://www.erlang.org/.
+#
+# Software distributed under the License is distributed on an "AS IS"
+# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+# the License for the specific language governing rights and limitations
+# under the License.
+#
+# %CopyrightEnd%
+
+RELTOOL_VSN = 0.5.2
+
+TICKETS = OTP-8254
+TICKETS_0_5_1 = OTP-8199
+TICKETS_0_5 = OTP-7949
+TICKETS_0_2_2 = OTP-7999
+TICKETS_2_2_1 = OTP-7840
+TICKETS_0_2 = OTP-7805