Skip to content

Commit 2c0b455

Browse files
guillaume-dequennesonartech
authored andcommitted
SONARPY-3337 Add validation step for licenses committed in the repository (#491)
GitOrigin-RevId: 8ebf686f77cb4e452f2bbccc49736f79e12ed541
1 parent 1488fc9 commit 2c0b455

3 files changed

Lines changed: 171 additions & 43 deletions

File tree

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
<pluginApiMinVersion>9.9</pluginApiMinVersion>
8484

8585
<skip.its>true</skip.its>
86+
<skipLicenseValidation>false</skipLicenseValidation>
8687

8788
<!-- versions -->
8889
<commons.io.version>2.20.0</commons.io.version>

sonar-python-plugin/pom.xml

Lines changed: 85 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@
145145
<errorRemedy>failFast</errorRemedy>
146146
<writeVersions>false</writeVersions>
147147
<socketTimeout>20000</socketTimeout>
148-
<licensesOutputDirectory>${project.basedir}/src/main/resources/licenses/THIRD_PARTY_LICENSES</licensesOutputDirectory>
148+
<licensesOutputDirectory>${project.build.directory}/temp-licenses/THIRD_PARTY_LICENSES</licensesOutputDirectory>
149149
<licensesOutputFile>${project.build.directory}/licenses.xml</licensesOutputFile>
150150
<organizeLicensesByDependencies>true</organizeLicensesByDependencies>
151151
<excludedGroups>com.sonarsource|org.sonarsource.python|org.sonarsource.analyzer-commons|org.sonarsource.sslr|jaxen</excludedGroups>
@@ -208,63 +208,37 @@
208208
</includes>
209209
</resource>
210210
</resources>
211-
<outputDirectory>${project.basedir}/src/main/resources/licenses</outputDirectory>
211+
<outputDirectory>${project.build.directory}/temp-licenses</outputDirectory>
212212
</configuration>
213213
</execution>
214214
</executions>
215215
</plugin>
216216

217217
<plugin>
218-
<artifactId>maven-antrun-plugin</artifactId>
219-
<version>3.1.0</version>
218+
<groupId>org.codehaus.mojo</groupId>
219+
<artifactId>exec-maven-plugin</artifactId>
220+
<version>3.5.1</version>
220221
<executions>
221222
<execution>
222-
<id>check-license-files</id>
223+
<id>validate-license-files</id>
223224
<phase>verify</phase>
224-
<configuration>
225-
<target>
226-
<fileset id="invalid.files" dir="${project.basedir}/src/main/resources/licenses">
227-
<exclude name="**/*.txt"/>
228-
<include name="**/*"/>
229-
</fileset>
230-
<condition property="has.invalid.files">
231-
<resourcecount refid="invalid.files" when="greater" count="0"/>
232-
</condition>
233-
<fail message="There are files in ${project.build.directory}/licenses/ that do not end with .txt">
234-
<condition>
235-
<isset property="has.invalid.files"/>
236-
</condition>
237-
</fail>
238-
</target>
239-
</configuration>
240225
<goals>
241-
<goal>run</goal>
226+
<goal>exec</goal>
242227
</goals>
228+
<configuration>
229+
<skip>${skipLicenseValidation}</skip>
230+
<executable>python</executable>
231+
<arguments>
232+
<argument>${project.basedir}/../tools/validate_licenses.py</argument>
233+
<argument>--temp_licenses=${project.build.directory}/temp-licenses</argument>
234+
<argument>--committed_licenses=${project.basedir}/src/main/resources/licenses</argument>
235+
</arguments>
236+
<useMavenLogger>true</useMavenLogger>
237+
</configuration>
243238
</execution>
244239
</executions>
245240
</plugin>
246241

247-
<plugin>
248-
<groupId>org.codehaus.mojo</groupId>
249-
<artifactId>build-helper-maven-plugin</artifactId>
250-
<version>3.6.0</version> <executions>
251-
<execution>
252-
<id>add-resource</id>
253-
<phase>generate-resources</phase>
254-
<goals>
255-
<goal>add-resource</goal>
256-
</goals>
257-
<configuration>
258-
<resources>
259-
<resource>
260-
<directory>${project.build.directory}/generated-resources</directory>
261-
</resource>
262-
</resources>
263-
</configuration>
264-
</execution>
265-
</executions>
266-
</plugin>
267-
268242
<plugin>
269243
<artifactId>maven-shade-plugin</artifactId>
270244
<executions>
@@ -329,4 +303,72 @@
329303
</plugins>
330304
</build>
331305

306+
<profiles>
307+
<profile>
308+
<id>updateLicenses</id>
309+
<build>
310+
<plugins>
311+
<!-- Clean existing licenses before regenerating -->
312+
<plugin>
313+
<artifactId>maven-antrun-plugin</artifactId>
314+
<version>3.1.0</version>
315+
<executions>
316+
<!-- Clean licenses directory first -->
317+
<execution>
318+
<id>clean-licenses</id>
319+
<phase>initialize</phase>
320+
<configuration>
321+
<target>
322+
<echo message="Cleaning existing license files before regeneration..."/>
323+
<delete dir="${project.basedir}/src/main/resources/licenses" failonerror="false"/>
324+
<mkdir dir="${project.basedir}/src/main/resources/licenses"/>
325+
</target>
326+
</configuration>
327+
<goals>
328+
<goal>run</goal>
329+
</goals>
330+
</execution>
331+
</executions>
332+
</plugin>
333+
334+
<!-- Disable license validation when updating -->
335+
<plugin>
336+
<groupId>org.codehaus.mojo</groupId>
337+
<artifactId>exec-maven-plugin</artifactId>
338+
<executions>
339+
<execution>
340+
<id>validate-license-files</id>
341+
<phase>none</phase>
342+
</execution>
343+
</executions>
344+
</plugin>
345+
346+
<!-- Override license-maven-plugin to write to actual resource directory -->
347+
<plugin>
348+
<groupId>org.codehaus.mojo</groupId>
349+
<artifactId>license-maven-plugin</artifactId>
350+
<configuration>
351+
<!-- Override to write directly to resources when updating -->
352+
<licensesOutputDirectory>${project.basedir}/src/main/resources/licenses/THIRD_PARTY_LICENSES</licensesOutputDirectory>
353+
</configuration>
354+
</plugin>
355+
356+
<!-- Override maven-resources-plugin to write to actual resource directory -->
357+
<plugin>
358+
<artifactId>maven-resources-plugin</artifactId>
359+
<executions>
360+
<execution>
361+
<id>copy-main-license</id>
362+
<configuration>
363+
<!-- Override to write directly to resources when updating -->
364+
<outputDirectory>${project.basedir}/src/main/resources/licenses</outputDirectory>
365+
</configuration>
366+
</execution>
367+
</executions>
368+
</plugin>
369+
</plugins>
370+
</build>
371+
</profile>
372+
</profiles>
373+
332374
</project>

tools/validate_licenses.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
3+
#
4+
# SonarQube Python Plugin
5+
# Copyright (C) 2011-2025 SonarSource SA
6+
# mailto:info AT sonarsource DOT com
7+
#
8+
# This program is free software; you can redistribute it and/or
9+
# modify it under the terms of the Sonar Source-Available License Version 1, as published by SonarSource SA.
10+
#
11+
# This program is distributed in the hope that it will be useful,
12+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14+
# See the Sonar Source-Available License for more details.
15+
#
16+
# You should have received a copy of the Sonar Source-Available License
17+
# along with this program; if not, see https://sonarsource.com/license/ssal/
18+
#
19+
20+
import argparse
21+
import filecmp
22+
import os
23+
import sys
24+
from pathlib import Path
25+
26+
27+
def main():
28+
parser = argparse.ArgumentParser(description='Validate license files against committed versions')
29+
parser.add_argument('--temp_licenses', help='Temporary licenses directory')
30+
parser.add_argument('--committed_licenses', help='Committed licenses directory')
31+
args = parser.parse_args()
32+
33+
temp_path = Path(args.temp_licenses)
34+
committed_path = Path(args.committed_licenses)
35+
36+
if not temp_path.exists():
37+
print(f"Error: Temporary licenses directory not found: {args.temp_licenses}")
38+
sys.exit(1)
39+
40+
if not committed_path.exists():
41+
print(f"Error: Committed licenses directory not found: {args.committed_licenses}")
42+
print("This might be the first time generating licenses.")
43+
print("To create the committed license files, run:")
44+
print(" mvn clean package -PupdateLicenses")
45+
sys.exit(1)
46+
47+
print("Validating generated license files against committed files...")
48+
49+
# Use filecmp.dircmp for cross-platform directory comparison (equivalent to diff -r)
50+
dcmp = filecmp.dircmp(args.temp_licenses, args.committed_licenses)
51+
differences = collect_differences(dcmp)
52+
53+
if differences:
54+
print("❌ License validation failed!")
55+
print("Generated license files differ from committed files.")
56+
print()
57+
print("Differences found:")
58+
for diff in differences:
59+
print(f" {diff}")
60+
print()
61+
print("To update the committed license files, run:")
62+
print(" mvn clean package -PupdateLicenses")
63+
print()
64+
print("Note: This will completely regenerate all license files and remove any stale ones.")
65+
sys.exit(1)
66+
67+
print("✅ License validation passed - generated files match committed files")
68+
69+
70+
def collect_differences(dcmp_obj, path_prefix=""):
71+
"""Recursively collect all differences between directories"""
72+
differences = []
73+
for file in dcmp_obj.left_only:
74+
differences.append(f"New file generated: {os.path.join(path_prefix, file)}")
75+
for file in dcmp_obj.right_only:
76+
differences.append(f"Missing generated file: {os.path.join(path_prefix, file)}")
77+
for file in dcmp_obj.diff_files:
78+
differences.append(f"Content differs: {os.path.join(path_prefix, file)}")
79+
for subdir_name, subdir_dcmp in dcmp_obj.subdirs.items():
80+
differences.extend(collect_differences(subdir_dcmp, os.path.join(path_prefix, subdir_name)))
81+
return differences
82+
83+
84+
if __name__ == '__main__':
85+
main()

0 commit comments

Comments
 (0)