diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 00db9fce4ce..3ccd694972a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -121,6 +121,9 @@ jobs: ############## - name: java run: mvn --batch-mode --update-snapshots verify -Pprintingbundle + # The printing bundle is unpacked over the WAR, so the two are checked together. + - name: "checking duplicated libraries" + run: python3 utility/build/check_duplicate_jars.py product/target/mapstore.war java/printing/target/mapstore-printing.zip build: runs-on: ubuntu-latest strategy: diff --git a/binary/bin-war/pom.xml b/binary/bin-war/pom.xml index 710d1a6fa87..55cf7c10306 100644 --- a/binary/bin-war/pom.xml +++ b/binary/bin-war/pom.xml @@ -36,17 +36,6 @@ maven-war-plugin ${maven-war-plugin.version} - - WEB-INF/lib/*spring*5.3.18*.jar, - WEB-INF/lib/json-lib-2.4-jdk15.jar, - WEB-INF/lib/msg-simple-1.1.jar, - WEB-INF/lib/btf-1.2.jar, - WEB-INF/lib/commons-io-2.1.jar, - WEB-INF/lib/commons-beanutils-1.8.0.jar, - WEB-INF/lib/commons-logging-1.1.1.jar, - WEB-INF/lib/jackson-coreutils-1.6.jar, - WEB-INF/lib/stax-ex-1.8.jar - it.geosolutions.mapstore diff --git a/docs/developer-guide/mapstore-migration-guide.md b/docs/developer-guide/mapstore-migration-guide.md index 2e470429221..8926906f05a 100644 --- a/docs/developer-guide/mapstore-migration-guide.md +++ b/docs/developer-guide/mapstore-migration-guide.md @@ -124,7 +124,7 @@ The following changes must be applied to your project (see the updated [project - 9.0.116 - 2.16.1 + 10.1.55 -+ 2.19.4 ++ 2.22.3 - 5.3.39 - 5.7.13 @@ -236,6 +236,178 @@ Copy the `logging.properties` file from the MapStore repository (`product/cargo/ **CI/CD**: update your build infrastructure to use JDK 17 as the build JDK. +### Duplicated libraries in the WAR + +The GeoStore and http-proxy WARs are added to your project as *overlays*: their `WEB-INF/lib` folder is copied as it is into your WAR. Some of those libraries (Jackson, Log4j, SLF4J, Micrometer, commons-io, ...) are also brought by MapStore, often in a different version, so the final WAR contains two versions of the same library, and which one is loaded depends on the servlet container. + +The steps below remove the duplicates. They change only two files of your project: `pom.xml` and `web/pom.xml`. The [project templates](https://github.com/geosolutions-it/MapStore2/tree/master/project/standard/templates) contain the final result, if you want to compare. + +#### 1. `pom.xml` (in the root folder of your project) + +**1.1** In the `` section, add these two lines right after the `jackson.version` line (updated to `2.22.3` in the previous section): + +```diff + 2.22.3 ++ 32.0.0-jre ++ 3.0.2 +``` + +**1.2** In the `` section, find the `jackson-bom` dependency and add the two dependencies below right after it (after its closing `` tag): + +```diff + + com.fasterxml.jackson + jackson-bom + ${jackson.version} + pom + import + ++ ++ com.google.guava ++ guava ++ ${guava.version} ++ ++ ++ com.google.code.findbugs ++ jsr305 ++ ${jsr305.version} ++ +``` + +#### 2. `web/pom.xml` + +**2.1** The servlet API is provided by Tomcat and must not be packaged in the WAR. In the main `` section (at the top of the file), find the `jakarta.servlet-api` dependency and add the `` line: + +```diff + + jakarta.servlet + jakarta.servlet-api + ${jakarta.servlet-api.version} ++ provided + +``` + +**2.2** Find the `maven-war-plugin` plugin. Inside its ``, delete the whole `` block: it lists versions that are not used anymore. + +```diff + +- +- WEB-INF/lib/*spring*5.3.18*.jar, +- WEB-INF/lib/json-lib-2.4-jdk15.jar, +- WEB-INF/lib/msg-simple-1.1.jar, +- WEB-INF/lib/btf-1.2.jar, +- WEB-INF/lib/commons-io-2.1.jar, +- WEB-INF/lib/commons-beanutils-1.8.0.jar, +- WEB-INF/lib/commons-logging-1.1.1.jar, +- WEB-INF/lib/jackson-coreutils-1.6.jar, +- WEB-INF/lib/stax-ex-1.8.jar +- + +``` + +**2.3** In the same ``, inside ``, add an `` block to each of the two overlays, right after their `` line. If an overlay already has an `` block, keep the `` lines it has and add the new ones to it. + +```diff + + + it.geosolutions.geostore + geostore-webapp ++ ++ WEB-INF/lib/commons-io-*.jar ++ WEB-INF/lib/jackson-annotations-*.jar ++ WEB-INF/lib/jackson-core-*.jar ++ WEB-INF/lib/jackson-databind-*.jar ++ WEB-INF/lib/log4j-api-*.jar ++ WEB-INF/lib/log4j-core-*.jar ++ WEB-INF/lib/log4j-slf4j-impl-*.jar ++ WEB-INF/lib/slf4j-api-*.jar ++ WEB-INF/lib/micrometer-commons-*.jar ++ WEB-INF/lib/micrometer-observation-*.jar ++ WEB-INF/lib/asm-2.*.jar ++ + + + proxy + http_proxy ++ ++ WEB-INF/lib/commons-io-*.jar ++ WEB-INF/lib/log4j-api-*.jar ++ WEB-INF/lib/log4j-core-*.jar ++ WEB-INF/lib/log4j-slf4j2-impl-*.jar ++ WEB-INF/lib/slf4j-api-*.jar ++ WEB-INF/lib/commons-codec-*.jar ++ WEB-INF/lib/jetty-*.jar ++ + + +``` + +**2.4** Find the `` with `printing` (near the end of the file). Inside it there is a `` section with another `jakarta.servlet-api` dependency: add the same `` line as in step 2.1. + +```diff + + jakarta.servlet + jakarta.servlet-api + ${jakarta.servlet-api.version} ++ provided + +``` + +**2.5** In the same `printing` profile, find the `print-lib` dependency. In its ``: + +- delete the `commons-codec` exclusion: + +```diff +- +- commons-codec +- commons-codec +- +``` + +- add these five exclusions before the closing `` tag (JAXB is already shipped by GeoStore, HTTP Client 5 by http-proxy): + +```diff ++ ++ org.glassfish.jaxb ++ jaxb-runtime ++ ++ ++ jakarta.xml.bind ++ jakarta.xml.bind-api ++ ++ ++ org.apache.httpcomponents.client5 ++ httpclient5 ++ ++ ++ org.apache.httpcomponents.core5 ++ httpcore5 ++ ++ ++ org.apache.httpcomponents.core5 ++ httpcore5-h2 ++ + +``` + +#### 3. Check the result + +From the root folder of your project, build the WAR and run the check script shipped with MapStore (Python 3 is required): + +```bash +mvn clean install +python3 MapStore2/utility/build/check_duplicate_jars.py web/target/*.war +``` + +If you use the printing module, repeat the check with the `printing` profile: + +```bash +mvn clean install -Pprinting +python3 MapStore2/utility/build/check_duplicate_jars.py web/target/*.war +``` + +The script must print `No duplicated libraries`. Otherwise it lists the libraries found in more than one version: if they come from a dependency you added to your project, exclude the older version. + ### New `viewFramingFilter` in `web.xml` Add the following filter to your project's `web/src/main/webapp/WEB-INF/web.xml`, mapped on `/index.html` (see the updated [project template](https://github.com/geosolutions-it/MapStore2/blob/master/project/standard/templates/web/src/main/webapp/WEB-INF/web.xml) for reference). It relies on `org.apache.catalina.filters.HttpHeaderSecurityFilter`, already available with the Tomcat 10.1 dependency required by the upgrade above: diff --git a/java/printing/assembly/mapstore-printing.xml b/java/printing/assembly/mapstore-printing.xml index cdd424ee7dc..a47df766eb7 100644 --- a/java/printing/assembly/mapstore-printing.xml +++ b/java/printing/assembly/mapstore-printing.xml @@ -13,67 +13,39 @@ ${project.build.directory}/mapstore-printing/WEB-INF **/*.xml - **/print-lib*.jar - **/aircompressor-*.jar - **/apiguardian-api-*.jar - **/avalon-*.jar - **/batik-*.jar - **/bcmail-*.jar - **/bcprov-*.jar - **/bigint-*.jar - **/commons-lang*.jar - **/commons-httpclient-*.jar - **/commons-io-*.jar - **/commons-text-*.jar - **/disruptor-*.jar - **/ejml-*.jar - **/fontbox-*.jar - **/fop-*.jar - **/GeographicLib-*.jar - **/gt-*.jar - **/guava-*.jar - **/httpclient-*.jar - **/httpcore-*.jar - **/imageio-*.jar - **/indriya-*.jar - **/hsqldb-*.jar - **/itext-*.jar - **/itextpdf-*.jar - **/jai*.jar - **/jaxb-*.jar - **/jaxb-api-*.jar - **/jdom2-*.jar - **/jempbox-*.jar - **/jgridshift-*.jar - **/json-20*.jar - **/jackson-dataformat-yaml-*.jar - **/javax.inject-*.jar - **/jts-core-*.jar - **/jt-*.jar - **/jyaml-*.jar - **/metrics-*.jar - **/net.opengis.ows-*.jar - **/org.eclipse.emf.common-*.jar - **/org.eclipse.emf.ecore-*.jar - **/org.eclipse.emf.ecore.xmi-*.jar - **/org.w3.xlink-*.jar - **/openpdf-*.jar - **/pdfbox-*.jar - **/profiler-*.jar - **/re2j-*.jar - **/si-*.jar - **/slf4j-api-*.jar - **/snakeyaml-*.jar - **/systems-common-*.jar - **/txw2-*.jar - **/unit-*.jar - **/uom-*.jar - xalan-*.jar - **/xml-apis-*.jar - **/xml-commons-resolver*.jar - **/xmlgraphics-commons-*.jar - **/xercesImpl-*.jar + + + + /WEB-INF/lib + + ${artifact.artifactId}-${artifact.baseVersion}${dashClassifier?}.${artifact.extension} + false + runtime + + com.fasterxml.jackson.core:* + com.google.code.findbugs:* + com.google.errorprone:* + com.google.guava:* + com.google.j2objc:* + commons-io:* + commons-logging:* + io.micrometer:* + jakarta.activation:* + jakarta.annotation:* + jakarta.inject:* + org.apache.commons:commons-lang3 + org.apache.commons:commons-text + org.apache.logging.log4j:* + org.jspecify:* + org.slf4j:* + org.springframework:* + + + diff --git a/java/printing/pom.xml b/java/printing/pom.xml index 50fdb6aa709..a980d6f4413 100644 --- a/java/printing/pom.xml +++ b/java/printing/pom.xml @@ -14,41 +14,16 @@ http://www.geosolutionsgroup.com - + org.mapfish.print print-lib - - - - org.apache.httpcomponents - httpclient - - - org.apache.httpcomponents - httpcore - - mapstore-printing - - maven-dependency-plugin - - - process-classes - - copy-dependencies - - - ${project.build.directory}/mapstore-printing/WEB-INF/lib - - - - maven-resources-plugin ${maven-resources-plugin.version} diff --git a/java/web/pom.xml b/java/web/pom.xml index 6f2053debeb..5b2dd757eca 100644 --- a/java/web/pom.xml +++ b/java/web/pom.xml @@ -67,26 +67,47 @@ maven-war-plugin ${maven-war-plugin.version} - - WEB-INF/lib/*spring*5.3.18*.jar, - WEB-INF/lib/json-lib-2.4-jdk15.jar, - WEB-INF/lib/msg-simple-1.1.jar, - WEB-INF/lib/btf-1.2.jar, - WEB-INF/lib/commons-io-2.1.jar, - WEB-INF/lib/commons-beanutils-1.8.0.jar, - WEB-INF/lib/commons-logging-1.1.1.jar, - WEB-INF/lib/jackson-coreutils-1.6.jar, - WEB-INF/lib/stax-ex-1.8.jar - + it.geosolutions.geostore geostore-webapp - WEB-INF/classes/geostore-spring-security.xml + + WEB-INF/classes/geostore-spring-security.xml + WEB-INF/lib/commons-io-*.jar + WEB-INF/lib/jackson-annotations-*.jar + WEB-INF/lib/jackson-core-*.jar + WEB-INF/lib/jackson-databind-*.jar + WEB-INF/lib/log4j-api-*.jar + WEB-INF/lib/log4j-core-*.jar + WEB-INF/lib/log4j-slf4j-impl-*.jar + WEB-INF/lib/slf4j-api-*.jar + + WEB-INF/lib/micrometer-commons-*.jar + WEB-INF/lib/micrometer-observation-*.jar + + WEB-INF/lib/asm-2.*.jar + proxy http_proxy + + WEB-INF/lib/commons-io-*.jar + WEB-INF/lib/log4j-api-*.jar + WEB-INF/lib/log4j-core-*.jar + + WEB-INF/lib/log4j-slf4j2-impl-*.jar + WEB-INF/lib/slf4j-api-*.jar + + WEB-INF/lib/commons-codec-*.jar + + WEB-INF/lib/jetty-*.jar + diff --git a/pom.xml b/pom.xml index 1872c59eb22..e8dcecf2e8a 100644 --- a/pom.xml +++ b/pom.xml @@ -26,12 +26,13 @@ 3.2.2 - 2.14.0 + 2.22.0 1.2 6.0.0 4.0.3 2.4.2-geoserver 1.13 + 3.0.2 4.13.2 2.26.1 2.1.3 @@ -88,11 +89,12 @@ pom import - + com.google.guava guava + ${guava.version} org.checkerframework @@ -100,6 +102,11 @@ + + com.google.code.findbugs + jsr305 + ${jsr305.version} + @@ -147,10 +154,12 @@ ${mockito-core.version} test + jakarta.servlet jakarta.servlet-api ${jakarta.servlet-api.version} + provided org.apache.commons @@ -248,55 +257,14 @@ - + it.geosolutions.geostore geostore-webapp ${geostore-webapp.version} war runtime - - - org.springframework - spring-aop - - - org.springframework - spring-beans - - - org.springframework - spring-context - - - org.springframework - spring-core - - - org.springframework - spring-expression - - - org.springframework - spring-jcl - - - org.springframework - spring-jdbc - - - org.springframework - spring-orm - - - org.springframework - spring-tx - - - org.springframework - spring-web - - @@ -306,33 +274,20 @@ war runtime - + org.mapfish.print print-lib ${print-lib.version} - - - javax.xml.bind - jaxb-api - - - com.sun.xml.bind - jaxb-impl - org.glassfish.jaxb jaxb-runtime - org.glassfish.jaxb - txw2 - - - - com.sun.xml.fastinfoset - FastInfoset + jakarta.xml.bind + jakarta.xml.bind-api @@ -344,8 +299,16 @@ httpcore - jakarta.xml.bind - jakarta.xml.bind-api + org.apache.httpcomponents.client5 + httpclient5 + + + org.apache.httpcomponents.core5 + httpcore5 + + + org.apache.httpcomponents.core5 + httpcore5-h2 @@ -372,12 +335,6 @@ org.apache.logging.log4j log4j-slf4j-impl ${log4j.version} - - - org.slf4j - slf4j-api - - @@ -488,28 +445,12 @@ maven-antrun-plugin 3.1.0 + + org.apache.maven.plugins + maven-war-plugin + ${maven-war-plugin.version} + - - - - org.apache.maven.plugins - maven-war-plugin - 3.4.0 - - - WEB-INF/lib/*spring*5.3.18*.jar, - WEB-INF/lib/json-lib-2.4-jdk15.jar, - WEB-INF/lib/msg-simple-1.1.jar, - WEB-INF/lib/btf-1.2.jar, - WEB-INF/lib/commons-io-2.1.jar, - WEB-INF/lib/commons-beanutils-1.8.0.jar, - WEB-INF/lib/commons-logging-1.1.1.jar, - WEB-INF/lib/jackson-coreutils-1.6.jar, - WEB-INF/lib/stax-ex-1.8.jar - - - - diff --git a/product/pom.xml b/product/pom.xml index b18c751322d..9d17544a56a 100644 --- a/product/pom.xml +++ b/product/pom.xml @@ -251,17 +251,6 @@ maven-war-plugin ${maven-war-plugin.version} - - WEB-INF/lib/*spring*5.3.18*.jar, - WEB-INF/lib/json-lib-2.4-jdk15.jar, - WEB-INF/lib/msg-simple-1.1.jar, - WEB-INF/lib/btf-1.2.jar, - WEB-INF/lib/commons-io-2.1.jar, - WEB-INF/lib/commons-beanutils-1.8.0.jar, - WEB-INF/lib/commons-logging-1.1.1.jar, - WEB-INF/lib/jackson-coreutils-1.6.jar, - WEB-INF/lib/stax-ex-1.8.jar - it.geosolutions.mapstore @@ -362,15 +351,11 @@ - + org.mapfish.print print-lib - - commons-codec - commons-codec - com.fasterxml.jackson.core jackson-annotations @@ -391,15 +376,6 @@ org.springframework spring-context - - - org.apache.httpcomponents - httpclient - - - org.apache.httpcomponents - httpcore - diff --git a/project/standard/templates/pom.xml b/project/standard/templates/pom.xml index 19a73a052e7..d5481b93cb1 100644 --- a/project/standard/templates/pom.xml +++ b/project/standard/templates/pom.xml @@ -13,7 +13,9 @@ 8080 10.1.55 - 2.19.4 + 2.22.3 + 32.0.0-jre + 3.0.2 -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8989 2.6 3.4.0 @@ -48,6 +50,17 @@ pom import + + + com.google.guava + guava + ${guava.version} + + + com.google.code.findbugs + jsr305 + ${jsr305.version} + diff --git a/project/standard/templates/web/pom.xml b/project/standard/templates/web/pom.xml index b05476a443e..b43317774f9 100644 --- a/project/standard/templates/web/pom.xml +++ b/project/standard/templates/web/pom.xml @@ -57,11 +57,12 @@ ${mockito-core.version} test - + jakarta.servlet jakarta.servlet-api ${jakarta.servlet-api.version} + provided @@ -351,25 +352,46 @@ maven-war-plugin ${maven-war-plugin.version} - - WEB-INF/lib/*spring*5.3.18*.jar, - WEB-INF/lib/json-lib-2.4-jdk15.jar, - WEB-INF/lib/msg-simple-1.1.jar, - WEB-INF/lib/btf-1.2.jar, - WEB-INF/lib/commons-io-2.1.jar, - WEB-INF/lib/commons-beanutils-1.8.0.jar, - WEB-INF/lib/commons-logging-1.1.1.jar, - WEB-INF/lib/jackson-coreutils-1.6.jar, - WEB-INF/lib/stax-ex-1.8.jar - + it.geosolutions.geostore geostore-webapp + + WEB-INF/lib/commons-io-*.jar + WEB-INF/lib/jackson-annotations-*.jar + WEB-INF/lib/jackson-core-*.jar + WEB-INF/lib/jackson-databind-*.jar + WEB-INF/lib/log4j-api-*.jar + WEB-INF/lib/log4j-core-*.jar + WEB-INF/lib/log4j-slf4j-impl-*.jar + WEB-INF/lib/slf4j-api-*.jar + + WEB-INF/lib/micrometer-commons-*.jar + WEB-INF/lib/micrometer-observation-*.jar + + WEB-INF/lib/asm-2.*.jar + proxy http_proxy + + WEB-INF/lib/commons-io-*.jar + WEB-INF/lib/log4j-api-*.jar + WEB-INF/lib/log4j-core-*.jar + + WEB-INF/lib/log4j-slf4j2-impl-*.jar + WEB-INF/lib/slf4j-api-*.jar + + WEB-INF/lib/commons-codec-*.jar + + WEB-INF/lib/jetty-*.jar + @@ -479,6 +501,7 @@ jakarta.servlet jakarta.servlet-api ${jakarta.servlet-api.version} + provided jakarta.xml.ws @@ -488,16 +511,12 @@ - + org.mapfish.print print-lib ${print-lib.version} - - commons-codec - commons-codec - com.fasterxml.jackson.core jackson-annotations @@ -518,6 +537,14 @@ org.springframework spring-context + + org.glassfish.jaxb + jaxb-runtime + + + jakarta.xml.bind + jakarta.xml.bind-api + org.apache.httpcomponents @@ -527,6 +554,18 @@ org.apache.httpcomponents httpcore + + org.apache.httpcomponents.client5 + httpclient5 + + + org.apache.httpcomponents.core5 + httpcore5 + + + org.apache.httpcomponents.core5 + httpcore5-h2 + diff --git a/utility/build/check_duplicate_jars.py b/utility/build/check_duplicate_jars.py new file mode 100644 index 00000000000..abfb5c8e9e1 --- /dev/null +++ b/utility/build/check_duplicate_jars.py @@ -0,0 +1,118 @@ +""" +check_duplicate_jars.py + +Fails when the given WAR or ZIP archives, deployed together, put two versions of +the same library in WEB-INF/lib. Which of the two the servlet container loads +first depends on the environment, so such a WAR must not be released. + +The archives are merged the way they are deployed: the printing bundle is +unpacked over the MapStore WAR, so a jar with the same file name in both is +the same file, not a duplicate. + +A library is identified by the jar file name without its version +(e.g. log4j-api-2.26.1.jar -> log4j-api). Two jars with the same name are +reported only when they contain at least one class in common: this keeps apart +libraries that share an artifactId but not their packages, such as Jackson 2 +(com.fasterxml.jackson.core:jackson-core) and Jackson 3 +(tools.jackson.core:jackson-core). + +Usage example: + python utility/build/check_duplicate_jars.py \\ + product/target/mapstore.war \\ + java/printing/target/mapstore-printing.zip + +Exit status: 0 when no library is duplicated, 1 otherwise. +""" + +import argparse +import io +import os +import re +import sys +import zipfile +from collections import defaultdict + +LIB_ENTRY = re.compile(r"^WEB-INF/lib/([^/]+\.jar)$") +# the name ends before the first "-": guava-32.0.0-jre.jar -> guava +JAR_NAME = re.compile(r"^(.+?)-\d.*\.jar$") + + +def library_name(jar_file_name): + match = JAR_NAME.match(jar_file_name) + return match.group(1) if match else jar_file_name[: -len(".jar")] + + +def class_entries(jar_bytes): + with zipfile.ZipFile(io.BytesIO(jar_bytes)) as jar: + return { + name + for name in jar.namelist() + if name.endswith(".class") + and not name.startswith("META-INF/") + and not name.endswith("module-info.class") + } + + +def collect_jars(archives): + """Returns {jar file name: (archive path, jar bytes)}, merged as deployed.""" + jars = {} + for archive in archives: + with zipfile.ZipFile(archive) as zipped: + for entry in zipped.namelist(): + match = LIB_ENTRY.match(entry) + if match: + jars[match.group(1)] = (archive, zipped.read(entry)) + return jars + + +def find_duplicates(jars): + by_name = defaultdict(list) + for jar_file_name in jars: + by_name[library_name(jar_file_name)].append(jar_file_name) + + duplicates = [] + for name, jar_file_names in sorted(by_name.items()): + if len(jar_file_names) < 2: + continue + classes = {jar: class_entries(jars[jar][1]) for jar in jar_file_names} + clashing = sorted( + jar + for jar in jar_file_names + if any(classes[jar] & classes[other] for other in jar_file_names if other != jar) + ) + if clashing: + duplicates.append((name, clashing)) + return duplicates + + +def main(): + parser = argparse.ArgumentParser( + description="Fail when WAR/ZIP archives deployed together contain two versions of the same jar." + ) + parser.add_argument("archives", nargs="+", help="WAR or ZIP archives, in deploy order") + args = parser.parse_args() + + for archive in args.archives: + if not os.path.isfile(archive): + parser.error(f"archive not found: {archive}") + + jars = collect_jars(args.archives) + duplicates = find_duplicates(jars) + checked = ", ".join(args.archives) + + if not duplicates: + print(f"No duplicated libraries among {len(jars)} jars in {checked}") + return 0 + + in_github_actions = os.environ.get("GITHUB_ACTIONS") == "true" + print(f"Libraries packaged in more than one version in {checked}:") + for name, jar_file_names in duplicates: + found = ", ".join(f"{jar} ({jars[jar][0]})" for jar in jar_file_names) + print(f" {name}: {found}") + if in_github_actions: + print(f"::error title=Duplicated library {name}::{found}") + return 1 + + +if __name__ == "__main__": + sys.exit(main())