Skip to content

Commit 3fae94e

Browse files
CleanCommand: clear readonly attributes before deleting directories
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a30637d commit 3fae94e

1 file changed

Lines changed: 161 additions & 147 deletions

File tree

Lines changed: 161 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,162 @@
1-
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
2-
3-
using PostSharp.Engineering.BuildTools.Utilities;
4-
using System;
5-
using System.IO;
6-
7-
namespace PostSharp.Engineering.BuildTools.Build
8-
{
9-
/// <summary>
10-
/// Cleans the current repo from build artefacts.
11-
/// </summary>
12-
internal class CleanCommand : BaseCommand<BuildSettings>
13-
{
14-
protected override bool ExecuteCore( BuildContext context, BuildSettings settings )
15-
{
16-
return Execute( context, settings );
17-
}
18-
19-
public static bool Execute( BuildContext context, BuildSettings settings )
20-
{
21-
// Kill processes that may hold file locks.
22-
ProcessKiller.KillProcessesBeforeClean( context.Console );
23-
24-
var product = context.Product;
25-
26-
void DeleteDirectory( string directory )
27-
{
28-
if ( Directory.Exists( directory ) )
29-
{
30-
context.Console.WriteMessage( $"Deleting directory '{directory}'." );
31-
Directory.Delete( directory, true );
32-
}
33-
}
34-
35-
void CleanRecursive( string directory )
36-
{
37-
DeleteDirectory( Path.Combine( directory, "bin" ) );
38-
DeleteDirectory( Path.Combine( directory, "obj" ) );
39-
40-
foreach ( var subdirectory in Directory.EnumerateDirectories( directory ) )
41-
{
42-
if ( subdirectory == Path.Combine( context.RepoDirectory, product.EngineeringDirectory ) )
43-
{
44-
// Skip the engineering directory.
45-
continue;
46-
}
47-
48-
CleanRecursive( subdirectory );
49-
}
50-
}
51-
52-
// Clears NuGet global-packages cache of Metalama and PostSharp.Engineering packages to prevent using old or corrupted package.
53-
void CleanNugetCache()
54-
{
55-
// Kill the processes to release the locks on the NuGet cache.
56-
ProcessKiller.KillWellKnownProcesses( context.Console );
57-
58-
// Use dotnet command to locate nuget cache directory.
59-
var success = ToolInvocationHelper.InvokeTool(
60-
context.Console,
61-
"dotnet",
62-
"nuget locals global-packages -l",
63-
context.RepoDirectory,
64-
out _,
65-
out var output );
66-
67-
if ( !success )
68-
{
69-
context.Console.WriteWarning( "Couldn't locate NuGet cache directory, skipping cleaning it." );
70-
71-
return;
72-
}
73-
74-
// Get only directory location string.
75-
var nugetCacheDirectory = output.Split( ' ' )[1].Trim();
76-
var directoryInfo = new DirectoryInfo( nugetCacheDirectory );
77-
78-
// Delete all cached packages directories starting with 'Metalama'.
79-
foreach ( var dir in directoryInfo.EnumerateDirectories( "metalama*" ) )
80-
{
81-
DeleteDirectory( Path.Combine( nugetCacheDirectory, dir.Name ) );
82-
}
83-
84-
// Delete all cached packages directories starting with 'PostSharp.Engineering' but the current one.
85-
foreach ( var dir in directoryInfo.EnumerateDirectories( "postsharp.engineering*" ) )
86-
{
87-
foreach ( var subDir in dir.EnumerateDirectories() )
88-
{
89-
var directoryPath = Path.Combine( nugetCacheDirectory, dir.Name, subDir.Name );
90-
91-
if ( subDir.Name.Equals( VersionHelper.EngineeringVersion, StringComparison.OrdinalIgnoreCase ) )
92-
{
93-
context.Console.WriteMessage( $"Skipping directory '{directoryPath}'." );
94-
95-
continue;
96-
}
97-
98-
DeleteDirectory( directoryPath );
99-
}
100-
}
101-
}
102-
103-
// NugetCache must be automatically deleted only on TeamCity.
104-
if ( context is { IsContinuousIntegrationBuild: true, IsRunningUnderContainer: false } && !settings.NoNuGetCacheCleanup )
105-
{
106-
context.Console.WriteHeading( "Cleaning NuGet cache" );
107-
context.Console.WriteMessage( "The NuGet cache cleanup can be skipped using --no-nuget-cache-cleanup." );
108-
109-
CleanNugetCache();
110-
}
111-
112-
context.Console.WriteHeading( $"Cleaning {product.ProductName}" );
113-
114-
foreach ( var directory in product.AdditionalDirectoriesToClean )
115-
{
116-
DeleteDirectory( Path.Combine( context.RepoDirectory, directory ) );
117-
}
118-
119-
DeleteDirectory( product.GetPrivateArtifactsAbsoluteDirectory( context, settings.BuildConfiguration ) );
120-
121-
DeleteDirectory( product.GetPublicArtifactsAbsoluteDirectory( context ) );
122-
123-
DeleteDirectory(
124-
Path.Combine(
125-
context.RepoDirectory,
126-
product.LogsDirectory ) );
127-
128-
foreach ( var directory in Directory.GetDirectories( context.RepoDirectory ) )
129-
{
130-
switch ( Path.GetFileName( directory ) )
131-
{
132-
case "source-dependencies":
133-
case "dependencies":
134-
case ".sonarqube":
135-
case { } s when s == product.EngineeringDirectory:
136-
continue;
137-
138-
default:
139-
CleanRecursive( directory );
140-
141-
break;
142-
}
143-
}
144-
145-
return true;
146-
}
147-
}
1+
// Copyright (c) SharpCrafters s.r.o. See the LICENSE.md file in the root directory of this repository root for details.
2+
3+
using PostSharp.Engineering.BuildTools.Utilities;
4+
using System;
5+
using System.IO;
6+
7+
namespace PostSharp.Engineering.BuildTools.Build
8+
{
9+
/// <summary>
10+
/// Cleans the current repo from build artefacts.
11+
/// </summary>
12+
internal class CleanCommand : BaseCommand<BuildSettings>
13+
{
14+
protected override bool ExecuteCore( BuildContext context, BuildSettings settings )
15+
{
16+
return Execute( context, settings );
17+
}
18+
19+
private static void ClearReadOnlyAttributes( string directory )
20+
{
21+
foreach ( var file in Directory.EnumerateFiles( directory, "*", SearchOption.AllDirectories ) )
22+
{
23+
var attributes = File.GetAttributes( file );
24+
25+
if ( (attributes & FileAttributes.ReadOnly) != 0 )
26+
{
27+
File.SetAttributes( file, attributes & ~FileAttributes.ReadOnly );
28+
}
29+
}
30+
}
31+
32+
public static bool Execute( BuildContext context, BuildSettings settings )
33+
{
34+
// Kill processes that may hold file locks.
35+
ProcessKiller.KillProcessesBeforeClean( context.Console );
36+
37+
var product = context.Product;
38+
39+
void DeleteDirectory( string directory )
40+
{
41+
if ( Directory.Exists( directory ) )
42+
{
43+
context.Console.WriteMessage( $"Deleting directory '{directory}'." );
44+
ClearReadOnlyAttributes( directory );
45+
Directory.Delete( directory, true );
46+
}
47+
}
48+
49+
void CleanRecursive( string directory )
50+
{
51+
DeleteDirectory( Path.Combine( directory, "bin" ) );
52+
DeleteDirectory( Path.Combine( directory, "obj" ) );
53+
54+
foreach ( var subdirectory in Directory.EnumerateDirectories( directory ) )
55+
{
56+
if ( subdirectory == Path.Combine( context.RepoDirectory, product.EngineeringDirectory ) )
57+
{
58+
// Skip the engineering directory.
59+
continue;
60+
}
61+
62+
CleanRecursive( subdirectory );
63+
}
64+
}
65+
66+
// Clears NuGet global-packages cache of Metalama and PostSharp.Engineering packages to prevent using old or corrupted package.
67+
void CleanNugetCache()
68+
{
69+
// Kill the processes to release the locks on the NuGet cache.
70+
ProcessKiller.KillWellKnownProcesses( context.Console );
71+
72+
// Use dotnet command to locate nuget cache directory.
73+
var success = ToolInvocationHelper.InvokeTool(
74+
context.Console,
75+
"dotnet",
76+
"nuget locals global-packages -l",
77+
context.RepoDirectory,
78+
out _,
79+
out var output );
80+
81+
if ( !success )
82+
{
83+
context.Console.WriteWarning( "Couldn't locate NuGet cache directory, skipping cleaning it." );
84+
85+
return;
86+
}
87+
88+
// Get only directory location string.
89+
var nugetCacheDirectory = output.Split( ' ' )[1].Trim();
90+
var directoryInfo = new DirectoryInfo( nugetCacheDirectory );
91+
92+
// Delete all cached packages directories starting with 'Metalama'.
93+
foreach ( var dir in directoryInfo.EnumerateDirectories( "metalama*" ) )
94+
{
95+
DeleteDirectory( Path.Combine( nugetCacheDirectory, dir.Name ) );
96+
}
97+
98+
// Delete all cached packages directories starting with 'PostSharp.Engineering' but the current one.
99+
foreach ( var dir in directoryInfo.EnumerateDirectories( "postsharp.engineering*" ) )
100+
{
101+
foreach ( var subDir in dir.EnumerateDirectories() )
102+
{
103+
var directoryPath = Path.Combine( nugetCacheDirectory, dir.Name, subDir.Name );
104+
105+
if ( subDir.Name.Equals( VersionHelper.EngineeringVersion, StringComparison.OrdinalIgnoreCase ) )
106+
{
107+
context.Console.WriteMessage( $"Skipping directory '{directoryPath}'." );
108+
109+
continue;
110+
}
111+
112+
DeleteDirectory( directoryPath );
113+
}
114+
}
115+
}
116+
117+
// NugetCache must be automatically deleted only on TeamCity.
118+
if ( context is { IsContinuousIntegrationBuild: true, IsRunningUnderContainer: false } && !settings.NoNuGetCacheCleanup )
119+
{
120+
context.Console.WriteHeading( "Cleaning NuGet cache" );
121+
context.Console.WriteMessage( "The NuGet cache cleanup can be skipped using --no-nuget-cache-cleanup." );
122+
123+
CleanNugetCache();
124+
}
125+
126+
context.Console.WriteHeading( $"Cleaning {product.ProductName}" );
127+
128+
foreach ( var directory in product.AdditionalDirectoriesToClean )
129+
{
130+
DeleteDirectory( Path.Combine( context.RepoDirectory, directory ) );
131+
}
132+
133+
DeleteDirectory( product.GetPrivateArtifactsAbsoluteDirectory( context, settings.BuildConfiguration ) );
134+
135+
DeleteDirectory( product.GetPublicArtifactsAbsoluteDirectory( context ) );
136+
137+
DeleteDirectory(
138+
Path.Combine(
139+
context.RepoDirectory,
140+
product.LogsDirectory ) );
141+
142+
foreach ( var directory in Directory.GetDirectories( context.RepoDirectory ) )
143+
{
144+
switch ( Path.GetFileName( directory ) )
145+
{
146+
case "source-dependencies":
147+
case "dependencies":
148+
case ".sonarqube":
149+
case { } s when s == product.EngineeringDirectory:
150+
continue;
151+
152+
default:
153+
CleanRecursive( directory );
154+
155+
break;
156+
}
157+
}
158+
159+
return true;
160+
}
161+
}
148162
}

0 commit comments

Comments
 (0)